diff --git a/apps/cli/scripts/export-sdk-contract.ts b/apps/cli/scripts/export-sdk-contract.ts index 35eb690b5b..f5d9ae016f 100644 --- a/apps/cli/scripts/export-sdk-contract.ts +++ b/apps/cli/scripts/export-sdk-contract.ts @@ -98,6 +98,7 @@ function buildSdkContract() { if (p.flag && p.flag !== p.name) spec.flag = p.flag; if (p.required) spec.required = true; if (p.schema) spec.schema = p.schema; + if (p.description) spec.description = p.description; if (p.agentVisible === false) spec.agentVisible = false; return spec; }), diff --git a/apps/cli/src/cli/operation-params.ts b/apps/cli/src/cli/operation-params.ts index b12dec4c9a..3e45246d84 100644 --- a/apps/cli/src/cli/operation-params.ts +++ b/apps/cli/src/cli/operation-params.ts @@ -15,6 +15,14 @@ import { OPERATION_REQUIRES_DOCUMENT_CONTEXT_MAP, type OperationId, } from '@superdoc/document-api'; +import { CLI_OPERATION_COMMAND_KEYS } from './commands'; +import { + CLI_DOC_OPERATIONS, + CLI_OPERATION_IDS, + type CliOnlyOperation, + type CliOperationId, + type DocBackedCliOpId, +} from './operation-set'; import type { CliOperationConstraints, CliOperationMetadata, @@ -22,28 +30,37 @@ import type { CliOperationParamSpec, CliTypeSpec, } from './types'; -import { - CLI_DOC_OPERATIONS, - CLI_OPERATION_IDS, - type CliOperationId, - type CliOnlyOperation, - type DocBackedCliOpId, -} from './operation-set'; -import { CLI_OPERATION_COMMAND_KEYS } from './commands'; // --------------------------------------------------------------------------- // Envelope param templates (CLI transport — not in document-api) // --------------------------------------------------------------------------- -const DOC_PARAM: CliOperationParamSpec = { name: 'doc', kind: 'doc', type: 'string' }; -const SESSION_PARAM: CliOperationParamSpec = { name: 'sessionId', kind: 'flag', flag: 'session', type: 'string' }; +const DOC_PARAM: CliOperationParamSpec = { + name: 'doc', + kind: 'doc', + type: 'string', + description: 'Document path. Optional when a session is already open.', +}; +const SESSION_PARAM: CliOperationParamSpec = { + name: 'sessionId', + kind: 'flag', + flag: 'session', + type: 'string', + description: 'Session ID for multi-session workflows. Optional when only one session is open.', +}; const OUT_PARAM: CliOperationParamSpec = { name: 'out', kind: 'flag', type: 'string', agentVisible: false }; -const FORCE_PARAM: CliOperationParamSpec = { name: 'force', kind: 'flag', type: 'boolean' }; +const FORCE_PARAM: CliOperationParamSpec = { + name: 'force', + kind: 'flag', + type: 'boolean', + description: 'Bypass confirmation checks.', +}; const DRY_RUN_PARAM: CliOperationParamSpec = { name: 'dryRun', kind: 'flag', flag: 'dry-run', type: 'boolean', + description: 'Preview the result without applying changes.', }; const CHANGE_MODE_PARAM: CliOperationParamSpec = { name: 'changeMode', @@ -51,12 +68,14 @@ const CHANGE_MODE_PARAM: CliOperationParamSpec = { flag: 'change-mode', type: 'string', schema: { enum: ['direct', 'tracked'] } as CliTypeSpec, + description: 'Edit mode: "direct" applies changes immediately, "tracked" records as suggestions.', }; const EXPECTED_REVISION_PARAM: CliOperationParamSpec = { name: 'expectedRevision', kind: 'flag', flag: 'expected-revision', type: 'number', + agentVisible: false, }; const USER_NAME_PARAM: CliOperationParamSpec = { name: 'userName', @@ -226,43 +245,46 @@ function isSimpleType(schema: JsonSchema, $defs?: Record): b function jsonSchemaToTypeSpec(schema: JsonSchema, $defs?: Record): CliTypeSpec { schema = resolveRef(schema, $defs); + const desc = typeof schema.description === 'string' ? schema.description : undefined; - if ('const' in schema) return { const: schema.const } as CliTypeSpec; + let result: CliTypeSpec; - if (schema.oneOf) { - return { + if ('const' in schema) { + result = { const: schema.const } as CliTypeSpec; + } else if (schema.oneOf) { + result = { oneOf: (schema.oneOf as JsonSchema[]).map((s) => jsonSchemaToTypeSpec(s, $defs)), } as CliTypeSpec; - } - - if (schema.enum && Array.isArray(schema.enum)) { - return { + } else if (schema.enum && Array.isArray(schema.enum)) { + result = { oneOf: (schema.enum as unknown[]).map((v) => ({ const: v }) as CliTypeSpec), } as CliTypeSpec; - } - - if (schema.type === 'string') return { type: 'string' } as CliTypeSpec; - if (schema.type === 'number' || schema.type === 'integer') return { type: 'number' } as CliTypeSpec; - if (schema.type === 'boolean') return { type: 'boolean' } as CliTypeSpec; - - if (schema.type === 'array') { + } else if (schema.type === 'string') { + result = { type: 'string' } as CliTypeSpec; + } else if (schema.type === 'number' || schema.type === 'integer') { + result = { type: 'number' } as CliTypeSpec; + } else if (schema.type === 'boolean') { + result = { type: 'boolean' } as CliTypeSpec; + } else if (schema.type === 'array') { const items = (schema.items as JsonSchema) ?? {}; - return { type: 'array', items: jsonSchemaToTypeSpec(items, $defs) } as CliTypeSpec; - } - - if (schema.type === 'object') { + result = { type: 'array', items: jsonSchemaToTypeSpec(items, $defs) } as CliTypeSpec; + } else if (schema.type === 'object') { const properties: Record = {}; for (const [key, propSchema] of Object.entries((schema.properties as Record) ?? {})) { properties[key] = jsonSchemaToTypeSpec(propSchema, $defs); } - const result: CliTypeSpec = { type: 'object', properties } as CliTypeSpec; + result = { type: 'object', properties } as CliTypeSpec; if (schema.required && Array.isArray(schema.required)) { (result as { required: readonly string[] }).required = schema.required as string[]; } - return result; + } else { + result = { type: 'json' } as CliTypeSpec; } - return { type: 'json' } as CliTypeSpec; + if (desc) { + (result as { description?: string }).description = desc; + } + return result; } function deriveParamsFromInputSchema( @@ -301,14 +323,24 @@ function deriveParamsFromInputSchema( const isComplex = !isSimpleType(propSchema, $defs) && paramType === 'json'; const flagBase = camelToKebab(name); + const isRequired = required.has(name); const param: CliOperationParamSpec = { name, kind: isComplex ? 'jsonFlag' : 'flag', flag: isComplex ? `${flagBase}-json` : flagBase, type: paramType, - required: required.has(name), + required: isRequired, }; + // Propagate description from JSON Schema property. + // Check raw schema first (description may sit alongside $ref), then resolved schema. + const rawDesc = (rawPropSchema as JsonSchema).description; + const resolvedDesc = propSchema.description; + const desc = typeof rawDesc === 'string' ? rawDesc : typeof resolvedDesc === 'string' ? resolvedDesc : undefined; + if (desc) { + param.description = desc; + } + if (AGENT_HIDDEN_PARAM_NAMES.has(name)) { param.agentVisible = false; } @@ -441,18 +473,18 @@ const PARAM_EXCLUSIONS: Partial>> = { // These are convenience alternatives to --target-json; invoke-input.ts // normalizes them into canonical target objects before dispatch. const TEXT_TARGET_FLAT_PARAMS: CliOperationParamSpec[] = [ - { name: 'blockId', kind: 'flag', flag: 'block-id', type: 'string' }, - { name: 'start', kind: 'flag', type: 'number' }, - { name: 'end', kind: 'flag', type: 'number' }, + { name: 'blockId', kind: 'flag', flag: 'block-id', type: 'string', description: 'Block ID of the target paragraph.' }, + { name: 'start', kind: 'flag', type: 'number', description: 'Start offset within the block (character index).' }, + { name: 'end', kind: 'flag', type: 'number', description: 'End offset within the block (character index).' }, ]; const INSERT_FLAT_PARAMS: CliOperationParamSpec[] = [ - { name: 'blockId', kind: 'flag', flag: 'block-id', type: 'string' }, - { name: 'offset', kind: 'flag', type: 'number' }, + { name: 'blockId', kind: 'flag', flag: 'block-id', type: 'string', description: 'Block ID of the target paragraph.' }, + { name: 'offset', kind: 'flag', type: 'number', description: 'Character offset within the block for insertion.' }, ]; const LIST_TARGET_FLAT_PARAMS: CliOperationParamSpec[] = [ - { name: 'nodeId', kind: 'flag', flag: 'node-id', type: 'string' }, + { name: 'nodeId', kind: 'flag', flag: 'node-id', type: 'string', description: 'Node ID of the target list item.' }, ]; const FORMAT_OPERATION_IDS = CLI_DOC_OPERATIONS.filter((operationId): operationId is OperationId => @@ -461,82 +493,313 @@ const FORMAT_OPERATION_IDS = CLI_DOC_OPERATIONS.filter((operationId): operationI const EXTRA_CLI_PARAMS: Partial> = { 'doc.find': [ - { name: 'type', kind: 'flag', type: 'string' }, - { name: 'nodeType', kind: 'flag', flag: 'node-type', type: 'string' }, - { name: 'kind', kind: 'flag', type: 'string' }, - { name: 'pattern', kind: 'flag', type: 'string' }, - { name: 'mode', kind: 'flag', type: 'string' }, - { name: 'caseSensitive', kind: 'flag', flag: 'case-sensitive', type: 'boolean' }, - { name: 'select', kind: 'jsonFlag', flag: 'select-json', type: 'json' }, - { name: 'query', kind: 'jsonFlag', flag: 'query-json', type: 'json' }, + { + name: 'type', + kind: 'flag', + type: 'string', + description: "Selector type: 'text' for text search or 'node' for node type search.", + }, + { + name: 'nodeType', + kind: 'flag', + flag: 'node-type', + type: 'string', + description: 'Node type to match (paragraph, heading, table, listItem, etc.).', + }, + { name: 'kind', kind: 'flag', type: 'string', description: "Filter: 'block' or 'inline'." }, + { name: 'pattern', kind: 'flag', type: 'string', description: 'Text or regex pattern to match.' }, + { name: 'mode', kind: 'flag', type: 'string', description: "Match mode: 'contains' (substring) or 'regex'." }, + { + name: 'caseSensitive', + kind: 'flag', + flag: 'case-sensitive', + type: 'boolean', + description: 'Case-sensitive matching. Default: false.', + }, + { + name: 'select', + kind: 'jsonFlag', + flag: 'select-json', + type: 'json', + description: "Search selector as JSON: {type:'text', pattern:'...'} or {type:'node', nodeType:'...'}.", + }, + { name: 'query', kind: 'jsonFlag', flag: 'query-json', type: 'json', description: 'Query filter as JSON object.' }, ], 'doc.lists.list': [{ name: 'query', kind: 'jsonFlag', flag: 'query-json', type: 'json' }], - 'doc.getNode': [{ name: 'address', kind: 'jsonFlag', flag: 'address-json', type: 'json' }], + 'doc.getNode': [ + { + name: 'address', + kind: 'jsonFlag', + flag: 'address-json', + type: 'json', + description: 'Node address to retrieve (block or inline address object).', + }, + ], // Text-range operations: flat flags (--block-id, --start, --end) as shortcuts for --target-json 'doc.insert': [...INSERT_FLAT_PARAMS], 'doc.replace': [...TEXT_TARGET_FLAT_PARAMS], 'doc.delete': [...TEXT_TARGET_FLAT_PARAMS], 'doc.styles.apply': [ - { name: 'target', kind: 'jsonFlag', flag: 'target-json', type: 'json' }, - { name: 'patch', kind: 'jsonFlag', flag: 'patch-json', type: 'json' }, + { + name: 'target', + kind: 'jsonFlag', + flag: 'target-json', + type: 'json', + description: 'Text address or block address to apply styles to.', + }, + { + name: 'patch', + kind: 'jsonFlag', + flag: 'patch-json', + type: 'json', + description: 'Style patch object with run and/or paragraph properties to apply.', + }, ], 'doc.comments.create': [...TEXT_TARGET_FLAT_PARAMS], 'doc.comments.patch': [...TEXT_TARGET_FLAT_PARAMS], // List operations: flat flag (--node-id) as shortcut for --target-json, plus --input-json 'doc.lists.insert': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.indent': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.outdent': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], - 'doc.lists.create': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.attach': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], + 'doc.lists.create': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.attach': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], 'doc.lists.detach': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], - 'doc.lists.join': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.canJoin': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], + 'doc.lists.join': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.canJoin': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], 'doc.lists.separate': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.setLevel': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.setValue': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.continuePrevious': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.lists.canContinuePrevious': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], - 'doc.lists.setLevelRestart': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.applyTemplate': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.applyPreset': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.captureTemplate': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelNumbering': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelBullet': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelPictureBullet': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelAlignment': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelIndents': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelTrailingCharacter': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.setLevelMarkerFont': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.lists.clearLevelOverrides': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], + 'doc.lists.setLevelRestart': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.applyTemplate': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.applyPreset': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.captureTemplate': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelNumbering': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelBullet': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelPictureBullet': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelAlignment': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelIndents': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelTrailingCharacter': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.setLevelMarkerFont': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], + 'doc.lists.clearLevelOverrides': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, + ], 'doc.lists.convertToText': [ - { name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }, + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Operation input as JSON object.', + }, ...LIST_TARGET_FLAT_PARAMS, ], 'doc.blocks.list': [ @@ -545,15 +808,49 @@ const EXTRA_CLI_PARAMS: Partial> = { { name: 'nodeTypes', kind: 'jsonFlag', flag: 'node-types-json', type: 'json' }, ], 'doc.blocks.delete': [ - { name: 'nodeType', kind: 'flag', flag: 'node-type', type: 'string' }, - { name: 'nodeId', kind: 'flag', flag: 'node-id', type: 'string' }, + { + name: 'nodeType', + kind: 'flag', + flag: 'node-type', + type: 'string', + description: 'Block type of the node to delete.', + }, + { name: 'nodeId', kind: 'flag', flag: 'node-id', type: 'string', description: 'Node ID of the block to delete.' }, ], 'doc.blocks.deleteRange': [ - { name: 'start', kind: 'jsonFlag', flag: 'start-json', type: 'json' }, - { name: 'end', kind: 'jsonFlag', flag: 'end-json', type: 'json' }, + { + name: 'start', + kind: 'jsonFlag', + flag: 'start-json', + type: 'json', + description: 'Block address of the first block in the range to delete.', + }, + { + name: 'end', + kind: 'jsonFlag', + flag: 'end-json', + type: 'json', + description: 'Block address of the last block in the range to delete.', + }, + ], + 'doc.create.paragraph': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Full paragraph input as JSON (alternative to individual text/at params).', + }, + ], + 'doc.create.heading': [ + { + name: 'input', + kind: 'jsonFlag', + flag: 'input-json', + type: 'json', + description: 'Full heading input as JSON (alternative to individual text/level/at params).', + }, ], - 'doc.create.paragraph': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], - 'doc.create.heading': [{ name: 'input', kind: 'jsonFlag', flag: 'input-json', type: 'json' }], }; for (const operationId of FORMAT_OPERATION_IDS) { @@ -698,9 +995,9 @@ function buildDocBackedMetadata(): Record(); const mergedParams: CliOperationParamSpec[] = []; - for (const param of envelope) { - seenNames.add(param.name); - mergedParams.push(param); + for (const envelopeParam of envelope) { + seenNames.add(envelopeParam.name); + mergedParams.push(envelopeParam); } // Apply flag overrides and exclusions to schema params before merging diff --git a/apps/cli/src/cli/types.ts b/apps/cli/src/cli/types.ts index 530e295ec9..9d0cd2487b 100644 --- a/apps/cli/src/cli/types.ts +++ b/apps/cli/src/cli/types.ts @@ -38,6 +38,8 @@ export type CliOperationParamSpec = { type: 'string' | 'number' | 'boolean' | 'string[]' | 'json'; required?: boolean; schema?: CliTypeSpec; + /** Human-readable description for agent tool schemas. */ + description?: string; /** When false, param is a transport-envelope detail hidden from agent tool schemas. */ agentVisible?: boolean; }; diff --git a/apps/docs/document-api/reference/comments/create.mdx b/apps/docs/document-api/reference/comments/create.mdx index c05dcb9c13..69c34e7555 100644 --- a/apps/docs/document-api/reference/comments/create.mdx +++ b/apps/docs/document-api/reference/comments/create.mdx @@ -116,12 +116,15 @@ Returns a Receipt confirming the comment was created; reports NO_OP if the ancho "additionalProperties": false, "properties": { "parentCommentId": { + "description": "Parent comment ID for creating a threaded reply.", "type": "string" }, "target": { - "$ref": "#/$defs/TextAddress" + "$ref": "#/$defs/TextAddress", + "description": "Text range to anchor the comment: {kind:'text', blockId:'...', range:{start:N, end:N}}." }, "text": { + "description": "Comment text content.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/comments/list.mdx b/apps/docs/document-api/reference/comments/list.mdx index 2c540b219f..71cafad1b7 100644 --- a/apps/docs/document-api/reference/comments/list.mdx +++ b/apps/docs/document-api/reference/comments/list.mdx @@ -101,12 +101,15 @@ Returns a CommentsListResult with an array of comment threads and total count. "additionalProperties": false, "properties": { "includeResolved": { + "description": "When true, includes resolved comments in results. Default: false.", "type": "boolean" }, "limit": { + "description": "Maximum number of comments to return.", "type": "integer" }, "offset": { + "description": "Number of comments to skip for pagination.", "type": "integer" } }, diff --git a/apps/docs/document-api/reference/comments/patch.mdx b/apps/docs/document-api/reference/comments/patch.mdx index d450c8205a..d31875df2d 100644 --- a/apps/docs/document-api/reference/comments/patch.mdx +++ b/apps/docs/document-api/reference/comments/patch.mdx @@ -122,9 +122,11 @@ Returns a Receipt confirming the comment was updated; reports NO_OP if no fields "type": "string" }, "isInternal": { + "description": "When true, marks the comment as internal (hidden from external collaborators).", "type": "boolean" }, "status": { + "description": "Set comment status. Use 'resolved' to mark as resolved.", "enum": [ "resolved" ] @@ -133,6 +135,7 @@ Returns a Receipt confirming the comment was updated; reports NO_OP if no fields "$ref": "#/$defs/TextAddress" }, "text": { + "description": "Updated comment text.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/create/heading.mdx b/apps/docs/document-api/reference/create/heading.mdx index f6c6b5a21b..d9d9377a2f 100644 --- a/apps/docs/document-api/reference/create/heading.mdx +++ b/apps/docs/document-api/reference/create/heading.mdx @@ -120,6 +120,7 @@ Returns a CreateHeadingResult with the new heading block ID and address. "additionalProperties": false, "properties": { "at": { + "description": "Position: {kind:'documentEnd'} to append, {kind:'documentStart'} to prepend, or {kind:'before'|'after', target:{kind:'block', nodeType:'...', nodeId:'...'}} for relative placement.", "oneOf": [ { "additionalProperties": false, @@ -180,11 +181,13 @@ Returns a CreateHeadingResult with the new heading block ID and address. ] }, "level": { + "description": "Heading level (1-6).", "maximum": 6, "minimum": 1, "type": "integer" }, "text": { + "description": "Heading text content.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/create/paragraph.mdx b/apps/docs/document-api/reference/create/paragraph.mdx index 011cbcfdf0..8dc393d86d 100644 --- a/apps/docs/document-api/reference/create/paragraph.mdx +++ b/apps/docs/document-api/reference/create/paragraph.mdx @@ -118,6 +118,7 @@ Returns a CreateParagraphResult with the new paragraph block ID and address. "additionalProperties": false, "properties": { "at": { + "description": "Position: {kind:'documentEnd'} to append, {kind:'documentStart'} to prepend, or {kind:'before'|'after', target:{kind:'block', nodeType:'...', nodeId:'...'}} for relative placement.", "oneOf": [ { "additionalProperties": false, @@ -178,6 +179,7 @@ Returns a CreateParagraphResult with the new paragraph block ID and address. ] }, "text": { + "description": "Paragraph text content.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/delete.mdx b/apps/docs/document-api/reference/delete.mdx index 7d8ff8ddb3..ddec5d0c4a 100644 --- a/apps/docs/document-api/reference/delete.mdx +++ b/apps/docs/document-api/reference/delete.mdx @@ -206,10 +206,12 @@ Returns a TextMutationReceipt with applied status; receipt reports NO_OP if the "additionalProperties": false, "properties": { "behavior": { - "$ref": "#/$defs/DeleteBehavior" + "$ref": "#/$defs/DeleteBehavior", + "description": "Delete behavior: 'selection' (default) or 'exact'." }, "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." } }, "required": [ @@ -221,9 +223,11 @@ Returns a TextMutationReceipt with applied status; receipt reports NO_OP if the "additionalProperties": false, "properties": { "behavior": { - "$ref": "#/$defs/DeleteBehavior" + "$ref": "#/$defs/DeleteBehavior", + "description": "Delete behavior: 'selection' (default) or 'exact'." }, "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/format/apply.mdx b/apps/docs/document-api/reference/format/apply.mdx index fcffa4a0a1..2fbba8fff5 100644 --- a/apps/docs/document-api/reference/format/apply.mdx +++ b/apps/docs/document-api/reference/format/apply.mdx @@ -296,6 +296,7 @@ Returns a TextMutationReceipt confirming inline styles were applied to the targe "properties": { "inline": { "additionalProperties": false, + "description": "Inline formatting properties to apply. Set a property to apply it, use null to clear it. Example: {bold: true, italic: true} or {bold: null} to remove bold.", "minProperties": 1, "properties": { "bCs": { @@ -1104,7 +1105,8 @@ Returns a TextMutationReceipt confirming inline styles were applied to the targe "type": "object" }, "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." } }, "required": [ @@ -1118,6 +1120,7 @@ Returns a TextMutationReceipt confirming inline styles were applied to the targe "properties": { "inline": { "additionalProperties": false, + "description": "Inline formatting properties to apply. Set a property to apply it, use null to clear it. Example: {bold: true, italic: true} or {bold: null} to remove bold.", "minProperties": 1, "properties": { "bCs": { @@ -1926,6 +1929,7 @@ Returns a TextMutationReceipt confirming inline styles were applied to the targe "type": "object" }, "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/format/b-cs.mdx b/apps/docs/document-api/reference/format/b-cs.mdx index 8f86a99723..80797aa756 100644 --- a/apps/docs/document-api/reference/format/b-cs.mdx +++ b/apps/docs/document-api/reference/format/b-cs.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/bold.mdx b/apps/docs/document-api/reference/format/bold.mdx index 32ecaf3a41..fe3853c037 100644 --- a/apps/docs/document-api/reference/format/bold.mdx +++ b/apps/docs/document-api/reference/format/bold.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/border.mdx b/apps/docs/document-api/reference/format/border.mdx index 12d7f40580..bfbae64b08 100644 --- a/apps/docs/document-api/reference/format/border.mdx +++ b/apps/docs/document-api/reference/format/border.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -278,6 +279,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/caps.mdx b/apps/docs/document-api/reference/format/caps.mdx index 995710f5e1..02a21ec85e 100644 --- a/apps/docs/document-api/reference/format/caps.mdx +++ b/apps/docs/document-api/reference/format/caps.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/char-scale.mdx b/apps/docs/document-api/reference/format/char-scale.mdx index d2471a0aed..aa291b71dc 100644 --- a/apps/docs/document-api/reference/format/char-scale.mdx +++ b/apps/docs/document-api/reference/format/char-scale.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/color.mdx b/apps/docs/document-api/reference/format/color.mdx index 37ac61be42..22e1c86186 100644 --- a/apps/docs/document-api/reference/format/color.mdx +++ b/apps/docs/document-api/reference/format/color.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/contextual-alternates.mdx b/apps/docs/document-api/reference/format/contextual-alternates.mdx index 3f8244af31..aeb45080cc 100644 --- a/apps/docs/document-api/reference/format/contextual-alternates.mdx +++ b/apps/docs/document-api/reference/format/contextual-alternates.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/cs.mdx b/apps/docs/document-api/reference/format/cs.mdx index a8bebba92c..99ef299d98 100644 --- a/apps/docs/document-api/reference/format/cs.mdx +++ b/apps/docs/document-api/reference/format/cs.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/dstrike.mdx b/apps/docs/document-api/reference/format/dstrike.mdx index 67979b2c18..99146adc4b 100644 --- a/apps/docs/document-api/reference/format/dstrike.mdx +++ b/apps/docs/document-api/reference/format/dstrike.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/east-asian-layout.mdx b/apps/docs/document-api/reference/format/east-asian-layout.mdx index 2b20e07011..0d8dbdf672 100644 --- a/apps/docs/document-api/reference/format/east-asian-layout.mdx +++ b/apps/docs/document-api/reference/format/east-asian-layout.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -288,6 +289,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/em.mdx b/apps/docs/document-api/reference/format/em.mdx index b4d67fff7a..18639fd5e1 100644 --- a/apps/docs/document-api/reference/format/em.mdx +++ b/apps/docs/document-api/reference/format/em.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/emboss.mdx b/apps/docs/document-api/reference/format/emboss.mdx index e43699ea2b..92af06315d 100644 --- a/apps/docs/document-api/reference/format/emboss.mdx +++ b/apps/docs/document-api/reference/format/emboss.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/fit-text.mdx b/apps/docs/document-api/reference/format/fit-text.mdx index 0cdc8a3931..277edf3760 100644 --- a/apps/docs/document-api/reference/format/fit-text.mdx +++ b/apps/docs/document-api/reference/format/fit-text.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -257,6 +258,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/font-family.mdx b/apps/docs/document-api/reference/format/font-family.mdx index 114024c665..55ca401072 100644 --- a/apps/docs/document-api/reference/format/font-family.mdx +++ b/apps/docs/document-api/reference/format/font-family.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/font-size-cs.mdx b/apps/docs/document-api/reference/format/font-size-cs.mdx index 8f72148aec..ee261567d8 100644 --- a/apps/docs/document-api/reference/format/font-size-cs.mdx +++ b/apps/docs/document-api/reference/format/font-size-cs.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/font-size.mdx b/apps/docs/document-api/reference/format/font-size.mdx index cb6621916a..f348558814 100644 --- a/apps/docs/document-api/reference/format/font-size.mdx +++ b/apps/docs/document-api/reference/format/font-size.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/highlight.mdx b/apps/docs/document-api/reference/format/highlight.mdx index eb7e312ac7..6589574f92 100644 --- a/apps/docs/document-api/reference/format/highlight.mdx +++ b/apps/docs/document-api/reference/format/highlight.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/i-cs.mdx b/apps/docs/document-api/reference/format/i-cs.mdx index a75151e4e9..6b1ba24e17 100644 --- a/apps/docs/document-api/reference/format/i-cs.mdx +++ b/apps/docs/document-api/reference/format/i-cs.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/imprint.mdx b/apps/docs/document-api/reference/format/imprint.mdx index 5f528f372d..fcabb606db 100644 --- a/apps/docs/document-api/reference/format/imprint.mdx +++ b/apps/docs/document-api/reference/format/imprint.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/italic.mdx b/apps/docs/document-api/reference/format/italic.mdx index 3e071ad430..e85615d027 100644 --- a/apps/docs/document-api/reference/format/italic.mdx +++ b/apps/docs/document-api/reference/format/italic.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/kerning.mdx b/apps/docs/document-api/reference/format/kerning.mdx index 48811a6b2a..7fe00d1449 100644 --- a/apps/docs/document-api/reference/format/kerning.mdx +++ b/apps/docs/document-api/reference/format/kerning.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/lang.mdx b/apps/docs/document-api/reference/format/lang.mdx index 1578406b23..50d6928d63 100644 --- a/apps/docs/document-api/reference/format/lang.mdx +++ b/apps/docs/document-api/reference/format/lang.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -269,6 +270,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/letter-spacing.mdx b/apps/docs/document-api/reference/format/letter-spacing.mdx index 54eabf39fc..be7d83616b 100644 --- a/apps/docs/document-api/reference/format/letter-spacing.mdx +++ b/apps/docs/document-api/reference/format/letter-spacing.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/ligatures.mdx b/apps/docs/document-api/reference/format/ligatures.mdx index 8fe08d7c47..95df5a4bf0 100644 --- a/apps/docs/document-api/reference/format/ligatures.mdx +++ b/apps/docs/document-api/reference/format/ligatures.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/num-form.mdx b/apps/docs/document-api/reference/format/num-form.mdx index e761dd926e..296a0c0ded 100644 --- a/apps/docs/document-api/reference/format/num-form.mdx +++ b/apps/docs/document-api/reference/format/num-form.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/num-spacing.mdx b/apps/docs/document-api/reference/format/num-spacing.mdx index 6c97444b55..41c6b6ba3d 100644 --- a/apps/docs/document-api/reference/format/num-spacing.mdx +++ b/apps/docs/document-api/reference/format/num-spacing.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/o-math.mdx b/apps/docs/document-api/reference/format/o-math.mdx index 660dc4c254..29e1ac5589 100644 --- a/apps/docs/document-api/reference/format/o-math.mdx +++ b/apps/docs/document-api/reference/format/o-math.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/outline.mdx b/apps/docs/document-api/reference/format/outline.mdx index bb146e5be9..49ba07fe92 100644 --- a/apps/docs/document-api/reference/format/outline.mdx +++ b/apps/docs/document-api/reference/format/outline.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/paragraph/set-indentation.mdx b/apps/docs/document-api/reference/format/paragraph/set-indentation.mdx index 476bde0a5a..fb204d8cac 100644 --- a/apps/docs/document-api/reference/format/paragraph/set-indentation.mdx +++ b/apps/docs/document-api/reference/format/paragraph/set-indentation.mdx @@ -156,18 +156,22 @@ Returns a ParagraphMutationResult; reports NO_OP if indentation already matches. }, "properties": { "firstLine": { + "description": "First line indent in twips. Cannot be combined with hanging.", "minimum": 0, "type": "integer" }, "hanging": { + "description": "Hanging indent in twips. Cannot be combined with firstLine.", "minimum": 0, "type": "integer" }, "left": { + "description": "Left indentation in twips (1440 = 1 inch).", "minimum": 0, "type": "integer" }, "right": { + "description": "Right indentation in twips (1440 = 1 inch).", "minimum": 0, "type": "integer" }, diff --git a/apps/docs/document-api/reference/format/paragraph/set-spacing.mdx b/apps/docs/document-api/reference/format/paragraph/set-spacing.mdx index bd3fc4bc09..196e22cec5 100644 --- a/apps/docs/document-api/reference/format/paragraph/set-spacing.mdx +++ b/apps/docs/document-api/reference/format/paragraph/set-spacing.mdx @@ -155,18 +155,22 @@ Returns a ParagraphMutationResult; reports NO_OP if spacing already matches. }, "properties": { "after": { + "description": "Space after paragraph in twips (20 twips = 1pt).", "minimum": 0, "type": "integer" }, "before": { + "description": "Space before paragraph in twips (20 twips = 1pt).", "minimum": 0, "type": "integer" }, "line": { + "description": "Line spacing value. Meaning depends on lineRule. Must be provided together with lineRule.", "minimum": 1, "type": "integer" }, "lineRule": { + "description": "Line spacing rule. Required when 'line' is set.", "enum": [ "auto", "exact", diff --git a/apps/docs/document-api/reference/format/position.mdx b/apps/docs/document-api/reference/format/position.mdx index 39940d45b0..ad0cdfe755 100644 --- a/apps/docs/document-api/reference/format/position.mdx +++ b/apps/docs/document-api/reference/format/position.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -229,6 +230,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/r-fonts.mdx b/apps/docs/document-api/reference/format/r-fonts.mdx index 227190bc9c..3dbac89f08 100644 --- a/apps/docs/document-api/reference/format/r-fonts.mdx +++ b/apps/docs/document-api/reference/format/r-fonts.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -335,6 +336,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/r-style.mdx b/apps/docs/document-api/reference/format/r-style.mdx index b825018016..71a319fefd 100644 --- a/apps/docs/document-api/reference/format/r-style.mdx +++ b/apps/docs/document-api/reference/format/r-style.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -230,6 +231,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/rtl.mdx b/apps/docs/document-api/reference/format/rtl.mdx index b7863791cd..faa1ea6bf8 100644 --- a/apps/docs/document-api/reference/format/rtl.mdx +++ b/apps/docs/document-api/reference/format/rtl.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/shading.mdx b/apps/docs/document-api/reference/format/shading.mdx index f81e7d577b..3b2d488dc0 100644 --- a/apps/docs/document-api/reference/format/shading.mdx +++ b/apps/docs/document-api/reference/format/shading.mdx @@ -209,7 +209,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -269,6 +270,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/shadow.mdx b/apps/docs/document-api/reference/format/shadow.mdx index f2b2c0dfb2..38bf446ad1 100644 --- a/apps/docs/document-api/reference/format/shadow.mdx +++ b/apps/docs/document-api/reference/format/shadow.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/small-caps.mdx b/apps/docs/document-api/reference/format/small-caps.mdx index a987a6ba5b..bc5ed1908f 100644 --- a/apps/docs/document-api/reference/format/small-caps.mdx +++ b/apps/docs/document-api/reference/format/small-caps.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/snap-to-grid.mdx b/apps/docs/document-api/reference/format/snap-to-grid.mdx index 74edd6a4a0..adb6a5b259 100644 --- a/apps/docs/document-api/reference/format/snap-to-grid.mdx +++ b/apps/docs/document-api/reference/format/snap-to-grid.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/spec-vanish.mdx b/apps/docs/document-api/reference/format/spec-vanish.mdx index b211ca2047..31d40e438a 100644 --- a/apps/docs/document-api/reference/format/spec-vanish.mdx +++ b/apps/docs/document-api/reference/format/spec-vanish.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/strike.mdx b/apps/docs/document-api/reference/format/strike.mdx index e49c8c90cb..fbaba23b0c 100644 --- a/apps/docs/document-api/reference/format/strike.mdx +++ b/apps/docs/document-api/reference/format/strike.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/stylistic-sets.mdx b/apps/docs/document-api/reference/format/stylistic-sets.mdx index 29676bbd98..f9d7caa7d8 100644 --- a/apps/docs/document-api/reference/format/stylistic-sets.mdx +++ b/apps/docs/document-api/reference/format/stylistic-sets.mdx @@ -211,7 +211,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -250,6 +251,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/underline.mdx b/apps/docs/document-api/reference/format/underline.mdx index e1dc6a016b..73d079830f 100644 --- a/apps/docs/document-api/reference/format/underline.mdx +++ b/apps/docs/document-api/reference/format/underline.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -268,6 +269,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/vanish.mdx b/apps/docs/document-api/reference/format/vanish.mdx index 2edf8b3bfb..6126051111 100644 --- a/apps/docs/document-api/reference/format/vanish.mdx +++ b/apps/docs/document-api/reference/format/vanish.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/vert-align.mdx b/apps/docs/document-api/reference/format/vert-align.mdx index 904664afde..1bea037068 100644 --- a/apps/docs/document-api/reference/format/vert-align.mdx +++ b/apps/docs/document-api/reference/format/vert-align.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -233,6 +234,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/format/web-hidden.mdx b/apps/docs/document-api/reference/format/web-hidden.mdx index 3e30864629..f997a6dc37 100644 --- a/apps/docs/document-api/reference/format/web-hidden.mdx +++ b/apps/docs/document-api/reference/format/web-hidden.mdx @@ -206,7 +206,8 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "value": { "oneOf": [ @@ -228,6 +229,7 @@ Returns a TextMutationReceipt confirming the inline run property patch was appli "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "value": { diff --git a/apps/docs/document-api/reference/get-html.mdx b/apps/docs/document-api/reference/get-html.mdx index a911aed5c4..b6c2cc4fd6 100644 --- a/apps/docs/document-api/reference/get-html.mdx +++ b/apps/docs/document-api/reference/get-html.mdx @@ -64,6 +64,7 @@ _No fields._ "additionalProperties": false, "properties": { "unflattenLists": { + "description": "When true, flattens nested list structures in output. Default: false.", "type": "boolean" } }, diff --git a/apps/docs/document-api/reference/insert.mdx b/apps/docs/document-api/reference/insert.mdx index 66df5a1fd3..ee1abf30e3 100644 --- a/apps/docs/document-api/reference/insert.mdx +++ b/apps/docs/document-api/reference/insert.mdx @@ -178,9 +178,11 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/TextAddress" + "$ref": "#/$defs/TextAddress", + "description": "Insertion point: {kind:'text', blockId:'...', range:{start, end}}." }, "type": { + "description": "Content format: 'text' (default), 'markdown', or 'html'.", "enum": [ "text", "markdown", @@ -189,6 +191,7 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre "type": "string" }, "value": { + "description": "Text content to insert.", "type": "string" } }, @@ -201,6 +204,7 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre "additionalProperties": false, "properties": { "content": { + "description": "Document fragment to insert (structured content).", "oneOf": [ { "type": "object" @@ -215,6 +219,7 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre }, "nestingPolicy": { "additionalProperties": false, + "description": "Controls nesting behavior. tables: 'allow' permits inserting tables inside other tables.", "properties": { "tables": { "enum": [ @@ -226,6 +231,7 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre "type": "object" }, "placement": { + "description": "Where to place content relative to target: 'before', 'after', 'insideStart', or 'insideEnd'.", "enum": [ "before", "after", @@ -234,7 +240,8 @@ Returns an SDMutationReceipt with applied status; resolution reports a TextAddre ] }, "target": { - "$ref": "#/$defs/BlockNodeAddress" + "$ref": "#/$defs/BlockNodeAddress", + "description": "Block address for structural insertion: {kind:'block', nodeType:'...', nodeId:'...'}." } }, "required": [ diff --git a/apps/docs/document-api/reference/lists/create.mdx b/apps/docs/document-api/reference/lists/create.mdx index 78a4363832..afa27f6ff8 100644 --- a/apps/docs/document-api/reference/lists/create.mdx +++ b/apps/docs/document-api/reference/lists/create.mdx @@ -163,26 +163,31 @@ _No fields._ ], "properties": { "at": { - "$ref": "#/$defs/BlockAddress" + "$ref": "#/$defs/BlockAddress", + "description": "Required when mode is 'empty'. The paragraph to create the list at. Format: {kind:'block', nodeType:'paragraph', nodeId:''}." }, "kind": { + "description": "List type: 'bullet' for bullet points, 'ordered' for numbered lists.", "enum": [ "ordered", "bullet" ] }, "level": { + "description": "List nesting level (0-8). 0 is the top level.", "maximum": 8, "minimum": 0, "type": "integer" }, "mode": { + "description": "Required. Creation mode: 'empty' creates a new empty list at the paragraph specified by 'at'; 'fromParagraphs' converts existing paragraph(s) specified by 'target' into list items.", "enum": [ "empty", "fromParagraphs" ] }, "preset": { + "description": "Predefined list style preset. Overrides 'kind' with a specific numbering or bullet format.", "enum": [ "decimal", "decimalParenthesis", @@ -309,7 +314,8 @@ _No fields._ "type": "object" }, "target": { - "$ref": "#/$defs/BlockAddressOrRange" + "$ref": "#/$defs/BlockAddressOrRange", + "description": "Required when mode is 'fromParagraphs'. The paragraph(s) to convert into list items. Format: {kind:'block', nodeType:'paragraph', nodeId:''}." } }, "required": [ diff --git a/apps/docs/document-api/reference/lists/insert.mdx b/apps/docs/document-api/reference/lists/insert.mdx index 1312b3ea44..dfa0feb1aa 100644 --- a/apps/docs/document-api/reference/lists/insert.mdx +++ b/apps/docs/document-api/reference/lists/insert.mdx @@ -124,15 +124,18 @@ Returns a ListsInsertResult with the new list item address and block ID. "additionalProperties": false, "properties": { "position": { + "description": "Required. Insert position relative to target: 'before' or 'after'.", "enum": [ "before", "after" ] }, "target": { - "$ref": "#/$defs/ListItemAddress" + "$ref": "#/$defs/ListItemAddress", + "description": "The target list item. For 'insert': the item to insert relative to. For 'create' with mode 'fromParagraphs': use nodeType 'paragraph' instead. Format: {kind:'block', nodeType:'listItem', nodeId:''}." }, "text": { + "description": "Text content for the new list item.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/lists/set-type.mdx b/apps/docs/document-api/reference/lists/set-type.mdx index ffae4c53c3..b267cf733b 100644 --- a/apps/docs/document-api/reference/lists/set-type.mdx +++ b/apps/docs/document-api/reference/lists/set-type.mdx @@ -105,6 +105,7 @@ Returns a ListsMutateItemResult receipt; reports NO_OP if the list is already th "additionalProperties": false, "properties": { "continuity": { + "description": "Numbering continuity: 'preserve' keeps numbering; 'none' restarts.", "enum": [ "preserve", "none" diff --git a/apps/docs/document-api/reference/mutations/apply.mdx b/apps/docs/document-api/reference/mutations/apply.mdx index 0a57f2e1b2..4697b53092 100644 --- a/apps/docs/document-api/reference/mutations/apply.mdx +++ b/apps/docs/document-api/reference/mutations/apply.mdx @@ -223,18 +223,22 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "properties": { "atomic": { "const": true, + "description": "Must be true. All steps execute as one atomic transaction.", "type": "boolean" }, "changeMode": { + "description": "Required. Use 'direct' for immediate edits or 'tracked' for suggestions. Must always be provided.", "enum": [ "direct", "tracked" ] }, "expectedRevision": { + "description": "Document revision for optimistic concurrency. Mutation fails if document was modified since this revision.", "type": "string" }, "steps": { + "description": "Ordered array of mutation steps. Each step needs 'op' (text.rewrite, text.insert, text.delete, format.apply, or assert) and a 'where' targeting clause.", "items": { "oneOf": [ { @@ -407,19 +411,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -432,12 +440,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -464,7 +474,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -650,19 +661,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -675,12 +690,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -707,7 +724,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -778,19 +796,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -803,12 +825,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -835,7 +859,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -1754,19 +1779,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -1779,12 +1808,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -1811,7 +1842,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -1915,19 +1947,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -1940,12 +1976,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -1972,7 +2010,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ diff --git a/apps/docs/document-api/reference/mutations/preview.mdx b/apps/docs/document-api/reference/mutations/preview.mdx index ed6d4a6342..c32a0beb95 100644 --- a/apps/docs/document-api/reference/mutations/preview.mdx +++ b/apps/docs/document-api/reference/mutations/preview.mdx @@ -209,18 +209,22 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "properties": { "atomic": { "const": true, + "description": "Must be true. All steps execute as one atomic transaction.", "type": "boolean" }, "changeMode": { + "description": "Required. Use 'direct' for immediate edits or 'tracked' for suggestions. Must always be provided.", "enum": [ "direct", "tracked" ] }, "expectedRevision": { + "description": "Document revision for optimistic concurrency. Mutation fails if document was modified since this revision.", "type": "string" }, "steps": { + "description": "Ordered array of mutation steps. Each step needs 'op' (text.rewrite, text.insert, text.delete, format.apply, or assert) and a 'where' targeting clause.", "items": { "oneOf": [ { @@ -393,19 +397,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -418,12 +426,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -450,7 +460,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -636,19 +647,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -661,12 +676,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -693,7 +710,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -764,19 +782,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -789,12 +811,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -821,7 +845,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -1740,19 +1765,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -1765,12 +1794,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -1797,7 +1828,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -1901,19 +1933,23 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -1926,12 +1962,14 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -1958,7 +1996,8 @@ The runtime capability snapshot also exposes this allowlist at `planEngine.suppo ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ diff --git a/apps/docs/document-api/reference/query/match.mdx b/apps/docs/document-api/reference/query/match.mdx index 8fd898726c..93f5bf33aa 100644 --- a/apps/docs/document-api/reference/query/match.mdx +++ b/apps/docs/document-api/reference/query/match.mdx @@ -185,23 +185,28 @@ Returns a QueryMatchOutput with the resolved target address and cardinality meta "additionalProperties": false, "properties": { "includeNodes": { + "description": "When true, includes full node data in results. Default: false.", "type": "boolean" }, "limit": { + "description": "Maximum number of matches to return.", "minimum": 1, "type": "integer" }, "mode": { + "description": "Search mode: 'strict' (default, exact matching) or 'candidates' (returns scored potential matches).", "enum": [ "strict", "candidates" ] }, "offset": { + "description": "Number of matches to skip for pagination.", "minimum": 0, "type": "integer" }, "require": { + "description": "Match cardinality: 'any' (all matches), 'first' (only first), 'exactlyOne' (fail if != 1), 'all' (fail if 0).", "enum": [ "any", "first", @@ -210,24 +215,29 @@ Returns a QueryMatchOutput with the resolved target address and cardinality meta ] }, "select": { + "description": "Search selector. Use {type:'text', pattern:'...'} for text search or {type:'node', nodeType:'paragraph'|'heading'|...} for node search.", "oneOf": [ { "additionalProperties": false, "properties": { "caseSensitive": { + "description": "Case-sensitive matching. Default: false.", "type": "boolean" }, "mode": { + "description": "Match mode: 'contains' (substring) or 'regex'.", "enum": [ "contains", "regex" ] }, "pattern": { + "description": "Text or regex pattern to match.", "type": "string" }, "type": { - "const": "text" + "const": "text", + "description": "Must be 'text' for text pattern search." } }, "required": [ @@ -240,12 +250,14 @@ Returns a QueryMatchOutput with the resolved target address and cardinality meta "additionalProperties": false, "properties": { "kind": { + "description": "Filter: 'block' or 'inline'.", "enum": [ "block", "inline" ] }, "nodeType": { + "description": "Block type to match (paragraph, heading, table, listItem, etc.).", "enum": [ "paragraph", "heading", @@ -272,7 +284,8 @@ Returns a QueryMatchOutput with the resolved target address and cardinality meta ] }, "type": { - "const": "node" + "const": "node", + "description": "Must be 'node' for node type search." } }, "required": [ @@ -283,7 +296,8 @@ Returns a QueryMatchOutput with the resolved target address and cardinality meta ] }, "within": { - "$ref": "#/$defs/BlockNodeAddress" + "$ref": "#/$defs/BlockNodeAddress", + "description": "Limit search scope to within a specific block: {kind:'block', nodeType:'...', nodeId:'...'}." } }, "required": [ diff --git a/apps/docs/document-api/reference/replace.mdx b/apps/docs/document-api/reference/replace.mdx index d2f9c54be5..c8d49e8096 100644 --- a/apps/docs/document-api/reference/replace.mdx +++ b/apps/docs/document-api/reference/replace.mdx @@ -189,9 +189,11 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "additionalProperties": false, "properties": { "target": { - "$ref": "#/$defs/SelectionTarget" + "$ref": "#/$defs/SelectionTarget", + "description": "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle." }, "text": { + "description": "Replacement text content.", "type": "string" } }, @@ -205,9 +207,11 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "additionalProperties": false, "properties": { "ref": { + "description": "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", "type": "string" }, "text": { + "description": "Replacement text content.", "type": "string" } }, @@ -225,6 +229,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "additionalProperties": false, "properties": { "content": { + "description": "Document fragment to replace with (structured content).", "oneOf": [ { "type": "object" @@ -239,6 +244,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t }, "nestingPolicy": { "additionalProperties": false, + "description": "Controls nesting behavior. tables: 'allow' permits inserting tables inside other tables.", "properties": { "tables": { "enum": [ @@ -250,6 +256,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "type": "object" }, "target": { + "description": "Target block or selection to replace.", "oneOf": [ { "$ref": "#/$defs/BlockNodeAddress" @@ -270,6 +277,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "additionalProperties": false, "properties": { "content": { + "description": "Document fragment to replace with (structured content).", "oneOf": [ { "type": "object" @@ -284,6 +292,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t }, "nestingPolicy": { "additionalProperties": false, + "description": "Controls nesting behavior. tables: 'allow' permits inserting tables inside other tables.", "properties": { "tables": { "enum": [ @@ -295,6 +304,7 @@ Returns an SDMutationReceipt with applied status; receipt reports NO_OP if the t "type": "object" }, "ref": { + "description": "Reference handle from a previous search result.", "type": "string" } }, diff --git a/apps/docs/document-api/reference/track-changes/list.mdx b/apps/docs/document-api/reference/track-changes/list.mdx index c389519bdc..93a74ad898 100644 --- a/apps/docs/document-api/reference/track-changes/list.mdx +++ b/apps/docs/document-api/reference/track-changes/list.mdx @@ -101,12 +101,15 @@ Returns a TrackChangesListResult with an array of tracked change entries and tot "additionalProperties": false, "properties": { "limit": { + "description": "Maximum number of tracked changes to return.", "type": "integer" }, "offset": { + "description": "Number of tracked changes to skip for pagination.", "type": "integer" }, "type": { + "description": "Filter by change type: 'insert', 'delete', or 'format'.", "enum": [ "insert", "delete", diff --git a/evals/lib/checks.cjs b/evals/lib/checks.cjs index ca16c37333..14d5faacda 100644 --- a/evals/lib/checks.cjs +++ b/evals/lib/checks.cjs @@ -367,6 +367,149 @@ module.exports.usesDeleteOp = (output) => { return { pass: false, score: 0, reason: 'No delete or rewrite operation found' }; }; +// --- First-instinct checks (Group 2) --- +// For multi-step tasks where single-turn can't validate the full sequence. +// These give full credit for the ideal tool, partial credit for reading first, +// and zero for calling the wrong category. + +/** + * Build a first-instinct checker. + * @param {string[]} idealTools - Tools that show the model KNOWS the right tool (score 1.0) + * @param {string[]} acceptableTools - Reading-first tools that are valid strategy (score 0.7) + * @param {string} taskLabel - For error messages + */ +function firstInstinct(idealTools, acceptableTools, taskLabel) { + return (output) => { + if (!Array.isArray(output) || output.length === 0) { + return { pass: false, score: 0, reason: 'No tool calls' }; + } + const called = output.map((c) => c.function?.name).filter(Boolean); + // Check if any ideal tool was called + const idealHit = called.find((n) => idealTools.includes(n)); + if (idealHit) return { pass: true, score: 1, reason: `Called ${idealHit} (ideal)` }; + // Check if any acceptable tool was called + const acceptableHit = called.find((n) => acceptableTools.includes(n)); + if (acceptableHit) return { pass: true, score: 0.7, reason: `Called ${acceptableHit} (reads first, acceptable)` }; + // Wrong category + return { pass: false, score: 0, reason: `${taskLabel}: called ${called.join(', ')}, expected one of [${[...idealTools, ...acceptableTools].join(', ')}]` }; + }; +} + +module.exports.instinctEdit = firstInstinct( + [EDIT, MUTATIONS, SEARCH], + [GET_CONTENT], + 'edit task', +); +module.exports.instinctFormat = firstInstinct( + [FORMAT, MUTATIONS, SEARCH], + [GET_CONTENT], + 'format task', +); +module.exports.instinctComment = firstInstinct( + [COMMENT, SEARCH], + [GET_CONTENT], + 'comment task', +); +module.exports.instinctList = firstInstinct( + [LIST, SEARCH], + [GET_CONTENT], + 'list task', +); +module.exports.instinctTrackChanges = firstInstinct( + [TRACK_CHANGES, SEARCH, EDIT, MUTATIONS], + [GET_CONTENT], + 'tracked changes task', +); + +// --- Execution trace helpers (Level 2) --- +// These parse the full JSON output from superdoc-agent-gateway.mjs +// which includes { documentText, trace: [{step, toolCalls, toolResults}] } + +function parseExecOutput(output) { + if (typeof output === 'string') { + try { return JSON.parse(output); } catch { return null; } + } + return typeof output === 'object' ? output : null; +} + +/** Assert documentText contains a string. */ +module.exports.docContains = (output, context) => { + const expected = context?.vars?.assertContains; + if (!expected) return true; + const d = parseExecOutput(output); + if (!d?.documentText) return { pass: false, score: 0, reason: 'No documentText in output' }; + if (d.documentText.includes(expected)) return { pass: true, score: 1, reason: `Contains "${expected}"` }; + return { pass: false, score: 0, reason: `Missing "${expected}"` }; +}; + +/** Assert documentText does NOT contain a string. */ +module.exports.docNotContains = (output, context) => { + const unexpected = context?.vars?.assertNotContains; + if (!unexpected) return true; + const d = parseExecOutput(output); + if (!d?.documentText) return { pass: false, score: 0, reason: 'No documentText in output' }; + if (!d.documentText.includes(unexpected)) return { pass: true, score: 1, reason: `Does not contain "${unexpected}"` }; + return { pass: false, score: 0, reason: `Still contains "${unexpected}"` }; +}; + +/** Assert a tool was used at any point in the trace. */ +module.exports.traceUsesTool = (output, context) => { + const expected = context?.vars?.assertTool; + if (!expected) return true; + const d = parseExecOutput(output); + if (!d?.trace) return { pass: false, score: 0, reason: 'No trace data' }; + const allTools = d.trace.flatMap((s) => s.toolCalls.map((tc) => tc.tool)); + if (allTools.includes(expected)) return { pass: true, score: 1, reason: `Trace includes ${expected}` }; + return { pass: false, score: 0, reason: `${expected} never called. Trace: ${allTools.join(' → ')}` }; +}; + +/** Assert tool A was called before tool B in the trace. */ +module.exports.traceToolOrder = (output, context) => { + const first = context?.vars?.assertFirst; + const then = context?.vars?.assertThen; + if (!first || !then) return true; + const d = parseExecOutput(output); + if (!d?.trace) return { pass: false, score: 0, reason: 'No trace data' }; + const allTools = d.trace.flatMap((s) => s.toolCalls.map((tc) => tc.tool)); + const firstIdx = allTools.indexOf(first); + const thenIdx = allTools.indexOf(then); + if (firstIdx < 0) return { pass: false, score: 0, reason: `${first} never called` }; + if (thenIdx < 0) return { pass: false, score: 0, reason: `${then} never called` }; + if (firstIdx < thenIdx) return { pass: true, score: 1, reason: `${first} (step ${firstIdx}) → ${then} (step ${thenIdx})` }; + return { pass: false, score: 0, reason: `${then} called before ${first}` }; +}; + +/** Assert all tool calls succeeded (no errors in trace). */ +module.exports.traceAllOk = (output) => { + const d = parseExecOutput(output); + if (!d?.trace) return { pass: false, score: 0, reason: 'No trace data' }; + if (!d.toolCalls?.length) return { pass: false, score: 0, reason: 'No tool calls were made' }; + const failedTools = d.toolCalls.filter((tc) => !tc.ok); + if (failedTools.length > 0) { + const names = failedTools.map((tc) => `${tc.tool}: ${tc.error || 'failed'}`).join(', '); + return { pass: false, score: 0, reason: `Tool failures: ${names}` }; + } + return { pass: true, score: 1, reason: `All ${d.toolCalls.length} tool calls succeeded` }; +}; + +/** Assert the total number of steps is within a range. */ +module.exports.traceStepCount = (output, context) => { + const max = context?.vars?.assertMaxSteps || 10; + const d = parseExecOutput(output); + if (!d?.trace) return { pass: false, score: 0, reason: 'No trace data' }; + const count = d.trace.length; + if (count <= max) return { pass: true, score: 1, reason: `${count} steps (max ${max})` }; + return { pass: false, score: 0, reason: `${count} steps exceeds max ${max}` }; +}; + +/** Log the full tool sequence for debugging (always passes). */ +module.exports.traceLog = (output) => { + const d = parseExecOutput(output); + if (!d?.trace) return { pass: true, score: 1, reason: 'No trace' }; + const seq = d.trace.flatMap((s) => s.toolCalls.map((tc) => tc.tool)); + return { pass: true, score: 1, reason: `Trace: ${seq.join(' → ')} (${d.stepCount || d.trace.length} steps)` }; +}; + module.exports.usesRewriteOp = (output) => { // Check superdoc_mutations steps if (findMutations(output)) { diff --git a/evals/promptfooconfig.e2e.yaml b/evals/promptfooconfig.e2e.yaml index 2f2df2e3db..eec8d531f2 100644 --- a/evals/promptfooconfig.e2e.yaml +++ b/evals/promptfooconfig.e2e.yaml @@ -21,17 +21,11 @@ providers: label: Claude Haiku 4.5 (Gateway) config: modelId: anthropic/claude-haiku-4.5 - - id: file://providers/superdoc-agent-gateway.mjs label: Gemini 2.5 Pro (Gateway) config: modelId: google/gemini-2.5-pro -defaultTest: - options: - transform: | - try { return JSON.parse(output).documentText; } catch { return output; } - evaluateOptions: maxConcurrency: 5 timeoutMs: 120000 diff --git a/evals/providers/superdoc-agent-gateway.mjs b/evals/providers/superdoc-agent-gateway.mjs index 35195a8719..f2ccce434c 100644 --- a/evals/providers/superdoc-agent-gateway.mjs +++ b/evals/providers/superdoc-agent-gateway.mjs @@ -65,10 +65,10 @@ function convertTool(fn, sdk, client, toolLog) { const cleaned = cleanArgs(args); try { const result = await sdk.dispatchSuperDocTool(client, fn.name, cleaned); - toolLog.push({ tool: fn.name, ok: true }); + toolLog.push({ tool: fn.name, args: cleaned, ok: true }); return result; } catch (err) { - toolLog.push({ tool: fn.name, ok: false }); + toolLog.push({ tool: fn.name, args: cleaned, ok: false, error: err.message }); return { ok: false, error: err.message }; } }, @@ -152,6 +152,22 @@ export default class SuperDocAgentGatewayProvider { if (keepFile && outputPath) copyFileSync(docPath, outputPath); cleanupTemp(docPath, stateDir); + // Build a rich trace from AI SDK steps for assertion consumption. + // Each step has: toolCalls[{toolName, args}], toolResults[{toolName, result}], text + const trace = steps.map((step, i) => ({ + step: i, + toolCalls: (step.toolCalls || []).map((tc) => ({ + tool: tc.toolName, + args: tc.args, + })), + toolResults: (step.toolResults || []).map((tr) => ({ + tool: tr.toolName, + ok: tr.result?.ok !== false, + })), + text: step.text || null, + finishReason: step.finishReason, + })); + const result = { output: JSON.stringify({ documentText, @@ -159,7 +175,8 @@ export default class SuperDocAgentGatewayProvider { toolCalls: toolLog, turns: toolLog.length, usage: totalUsage, - steps: steps.length, + stepCount: steps.length, + trace, }), }; writeCache(key, result); diff --git a/evals/tests/execution.yaml b/evals/tests/execution.yaml index 44a376a83a..94862bb8e4 100644 --- a/evals/tests/execution.yaml +++ b/evals/tests/execution.yaml @@ -1,295 +1,395 @@ -# Execution tests -- real document editing via SuperDoc CLI. +# Execution tests — real document editing via SuperDoc CLI + AI Gateway. # -# Assertion rules: -# 1. Every edit asserts new content exists AND old content is gone. -# 2. Every test verifies unrelated content is intact (no collateral damage). -# 3. Position tests use indexOf() for ordering. +# Focus: tool execution MECHANICS, not just content outcomes. +# The provider returns JSON: { documentText, trace[], toolCalls[], stepCount } +# +# Each test validates: +# 1. TRACE: correct tool sequence, all calls succeeded, reasonable step count +# 2. CONTENT: minimal check that the edit actually worked (one assertion) # ============================================================================= -# DOCUMENT.DOCX -- simple document with bullet lists -# Known content: "A list of list features", "We support bullets, symbols and more", -# "All sorts of bullets.", "Nested lists", "Numbers", "Or letters", -# "All sorts of lists are supported" +# DOCUMENT.DOCX — bullet lists # ============================================================================= -- description: 'Read: lists document content' +- description: 'Read: extracts document text' vars: fixture: document.docx + keepFile: true task: 'Get the full text of this document.' assert: - - type: contains - value: 'A list of list features' - - type: contains - value: 'We support bullets, symbols and more' - - type: contains - value: 'All sorts of lists are supported' + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('A list of list features')) return { pass: false, score: 0, reason: 'Content missing' }; + return { pass: true, score: 1, reason: 'Content extracted' }; + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: file://lib/checks.cjs:traceAllOk -- description: 'Replace: bullets to bullet points, all occurrences' +- description: 'Replace: bullets → bullet points' vars: fixture: document.docx + keepFile: true task: 'Replace "bullets" with "bullet points" everywhere in the document.' assert: - - type: contains - value: 'bullet points' - - type: not-contains - value: 'support bullets' - - type: not-contains - value: 'sorts of bullets' - - type: contains - value: 'Nested lists' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('bullet points') || t.includes('support bullets')) + return { pass: false, score: 0, reason: 'Replace failed' }; + return { pass: true, score: 1, reason: 'Content correct' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.trace) return { pass: true, score: 0.5, reason: 'No trace data' }; + const count = d.stepCount || d.trace.length; + if (count > 6) return { pass: false, score: 0, reason: `${count} steps — too many for a simple replace` }; + return { pass: true, score: 1, reason: `${count} steps` }; + metric: efficiency -- description: 'Insert: paragraph appended at end' +- description: 'Insert: paragraph at end' vars: fixture: document.docx + keepFile: true task: 'Add a new paragraph at the end of the document that says exactly "EVAL TEST PASSED".' assert: - - type: contains - value: 'EVAL TEST PASSED' - - type: contains - value: 'A list of list features' - type: javascript - value: "output.indexOf('EVAL TEST PASSED') > output.indexOf('All sorts of lists are supported')" + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('EVAL TEST PASSED')) return { pass: false, score: 0, reason: 'Missing' }; + return { pass: true, score: 1, reason: 'Paragraph added' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Insert: heading appended at end' +- description: 'Insert: heading at end' vars: fixture: document.docx + keepFile: true task: 'Add a heading "Summary" at the end of the document.' assert: - - type: contains - value: 'Summary' - type: javascript - value: "output.indexOf('Summary') > output.indexOf('All sorts of lists are supported')" + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('Summary')) return { pass: false, score: 0, reason: 'Missing' }; + return { pass: true, score: 1, reason: 'Heading added' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk # ============================================================================= -# MEMORANDUM.DOCX -- legal memo -# Known: "MEMORANDUM", "TO: General Counsel", "DATE: December 30, 2024", -# "$25,000,000", "$150,000,000", "March 1-31, 2025", "Acquiror", -# "reasonable best efforts", "[lawyer]", "Best regards," +# MEMORANDUM.DOCX — legal memo # ============================================================================= -- description: 'Read: memo with all key sections' +- description: 'Read: legal memo' vars: fixture: memorandum.docx + keepFile: true task: 'Get the full text of this document.' assert: - - type: contains - value: 'MEMORANDUM' - - type: contains - value: 'OPERATIONAL COVENANTS' - - type: contains - value: '$25,000,000' - - type: contains - value: 'Best regards,' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('MEMORANDUM') || !t.includes('$25,000,000')) + return { pass: false, score: 0, reason: 'Key content missing' }; + return { pass: true, score: 1, reason: 'All sections present' }; + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: file://lib/checks.cjs:traceAllOk -- description: 'Replace: party name, all occurrences, amounts untouched' +- description: 'Replace: party name globally' vars: fixture: memorandum.docx + keepFile: true task: 'Replace "General Counsel" with "Chief Legal Officer" everywhere in the document.' assert: - - type: contains - value: 'TO: Chief Legal Officer' - - type: contains - value: 'Dear Chief Legal Officer' - - type: not-contains - value: 'General Counsel' - - type: contains - value: '$25,000,000' - -- description: 'Replace: date changed, other dates untouched' - vars: - fixture: memorandum.docx - task: 'Replace the date "December 30, 2024" with "January 15, 2025".' - assert: - - type: contains - value: 'DATE: January 15, 2025' - - type: not-contains - value: 'December 30, 2024' - - type: contains - value: 'March 1-31, 2025' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('Chief Legal Officer') || t.includes('General Counsel')) + return { pass: false, score: 0, reason: 'Replace incomplete' }; + return { pass: true, score: 1, reason: 'All replaced' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Replace: $25M to $50M, $150M untouched' +- description: 'Replace: date precision ($25M/$150M untouched)' vars: fixture: memorandum.docx + keepFile: true task: 'Replace the acquisition limit of "$25,000,000" with "$50,000,000".' assert: - - type: contains - value: '$50,000,000' - - type: not-contains - value: '$25,000,000' - - type: contains - value: '$150,000,000' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('$50,000,000')) return { pass: false, score: 0, reason: 'Missing $50M' }; + if (t.includes('$25,000,000')) return { pass: false, score: 0, reason: '$25M still there' }; + if (!t.includes('$150,000,000')) return { pass: false, score: 0, reason: 'Collateral: $150M gone' }; + return { pass: true, score: 1, reason: 'Precise replace' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.trace) return { pass: true, score: 0.5, reason: 'No trace' }; + const tools = d.trace.flatMap(s => s.toolCalls.map(tc => tc.tool)); + const hasSearch = tools.some(t => t.includes('search')); + const hasEdit = tools.some(t => t.includes('edit') || t.includes('mutations')); + if (!hasSearch) return { pass: false, score: 0, reason: `No search step. Tools: ${tools.join('→')}` }; + if (!hasEdit) return { pass: false, score: 0, reason: `No edit step. Tools: ${tools.join('→')}` }; + return { pass: true, score: 1, reason: `Sequence: ${tools.join(' → ')}` }; + metric: sequence -- description: 'Replace: [lawyer] placeholder, signature intact' +- description: 'Replace: placeholder → name' vars: fixture: memorandum.docx + keepFile: true task: 'Replace "[lawyer]" with "Sarah Chen, Partner" at the end of the document.' assert: - - type: contains - value: 'Sarah Chen, Partner' - - type: not-contains - value: '[lawyer]' - - type: contains - value: 'Best regards,' - - type: javascript - value: "output.indexOf('Sarah Chen') > output.indexOf('Best regards')" + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('Sarah Chen') || t.includes('[lawyer]')) + return { pass: false, score: 0, reason: 'Replace failed' }; + return { pass: true, score: 1, reason: 'Replaced' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk -- description: 'Insert: heading + paragraph after signature' +- description: 'Insert: heading + paragraph at end' vars: fixture: memorandum.docx - task: 'Add a new heading "CONFIDENTIALITY NOTICE" at the end of the document, followed by a paragraph that says "This memorandum is privileged and confidential."' + keepFile: true + task: 'Add a heading "CONFIDENTIALITY NOTICE" at the end, followed by "This memorandum is privileged and confidential."' assert: - - type: contains - value: 'CONFIDENTIALITY NOTICE' - - type: contains - value: 'privileged and confidential' - type: javascript - value: "output.indexOf('CONFIDENTIALITY NOTICE') > output.indexOf('Best regards')" + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('CONFIDENTIALITY NOTICE') || !t.includes('privileged and confidential')) + return { pass: false, score: 0, reason: 'Missing content' }; + return { pass: true, score: 1, reason: 'Added at end' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Replace: Acquiror to Buyer, amounts untouched' +- description: 'Replace: Acquiror → Buyer globally' vars: fixture: memorandum.docx + keepFile: true task: 'Replace every instance of "Acquiror" with "Buyer" in this document.' assert: - - type: contains - value: 'Buyer' - - type: not-contains - value: 'Acquiror' - - type: contains - value: '$25,000,000' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('Buyer') || t.includes('Acquiror')) + return { pass: false, score: 0, reason: 'Replace incomplete' }; + return { pass: true, score: 1, reason: 'All replaced' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk -- description: 'Replace: go-shop date, Dec date untouched' +- description: 'Replace: go-shop date, Dec date intact' vars: fixture: memorandum.docx - task: 'Replace the go-shop period date "March 1-31, 2025" with "April 1-30, 2025" in the document.' + keepFile: true + task: 'Replace "March 1-31, 2025" with "April 1-30, 2025" in the document.' assert: - - type: contains - value: 'April 1-30, 2025' - - type: not-contains - value: 'March 1-31, 2025' - - type: contains - value: 'December 30, 2024' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('April 1-30, 2025') || t.includes('March 1-31, 2025')) + return { pass: false, score: 0, reason: 'Replace failed' }; + if (!t.includes('December 30, 2024')) + return { pass: false, score: 0, reason: 'Collateral: Dec date gone' }; + return { pass: true, score: 1, reason: 'Date replaced precisely' }; -- description: 'Replace: legal terms, covenants section intact' +- description: 'Replace: legal terms' vars: fixture: memorandum.docx - task: 'Replace "reasonable best efforts" with "commercially reasonable efforts" everywhere in the document.' + keepFile: true + task: 'Replace "reasonable best efforts" with "commercially reasonable efforts" everywhere.' assert: - - type: contains - value: 'commercially reasonable efforts' - - type: not-contains - value: 'reasonable best efforts' - - type: contains - value: 'Maintain the company in good standing' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('commercially reasonable efforts') || t.includes('reasonable best efforts')) + return { pass: false, score: 0, reason: 'Replace failed' }; + return { pass: true, score: 1, reason: 'Terms replaced' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk # ============================================================================= -# TABLE DOC -- document with tables +# TABLE DOC # ============================================================================= -- description: 'Table: reads headers and cell content' +- description: 'Table: read content' vars: fixture: table-doc.docx + keepFile: true task: 'Get the full text of this document.' assert: - - type: contains - value: 'Component' - - type: contains - value: 'Version' - - type: contains - value: 'January 22, 2026' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('Component') || !t.includes('Version')) + return { pass: false, score: 0, reason: 'Table content missing' }; + return { pass: true, score: 1, reason: 'Table read' }; + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Table: replace cell content, headers untouched' +- description: 'Table: replace cell content' vars: fixture: table-doc.docx + keepFile: true task: 'Replace every cell that says "Test" with "Production" in the table.' assert: - - type: contains - value: 'Production' - - type: contains - value: 'Component' - - type: contains - value: 'January 22, 2026' + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('Production')) + return { pass: false, score: 0, reason: 'Missing "Production"' }; + return { pass: true, score: 1, reason: 'Cells replaced' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog # ============================================================================= -# MULTI-STEP -- compound operations +# MULTI-STEP — compound operations, trace sequence matters most # ============================================================================= -- description: 'Multi-step: find section, insert paragraph after it' +- description: 'Multi-step: find section + insert after' vars: fixture: memorandum.docx + keepFile: true task: 'Find the section about "Alternative Transaction Proposals" and add a paragraph right after it that says "NOTE: Legal review required for this section."' assert: - - type: contains - value: 'NOTE: Legal review required for this section' - type: javascript - value: "output.indexOf('NOTE: Legal review required') > output.indexOf('Alternative Transaction Proposals')" + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('NOTE: Legal review required')) + return { pass: false, score: 0, reason: 'Note not added' }; + return { pass: true, score: 1, reason: 'Note inserted' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.trace) return { pass: true, score: 0.5, reason: 'No trace' }; + const tools = d.trace.flatMap(s => s.toolCalls.map(tc => tc.tool)); + if (tools.length < 2) return { pass: false, score: 0, reason: `Only ${tools.length} tool call — expected search + insert` }; + return { pass: true, score: 1, reason: `${tools.length} steps: ${tools.join(' → ')}` }; + metric: multi_step -- description: 'Multi-step: replace placeholder AND add disclaimer at end' +- description: 'Multi-step: replace + disclaimer' vars: fixture: memorandum.docx - task: 'Replace "[lawyer]" with "Sarah Chen, Partner" and then add a new heading "DISCLAIMER" at the very end of the document followed by a paragraph "This document does not constitute legal advice."' + keepFile: true + task: 'Replace "[lawyer]" with "Sarah Chen, Partner" and add a heading "DISCLAIMER" at the end followed by "This document does not constitute legal advice."' assert: - - type: contains - value: 'Sarah Chen, Partner' - - type: not-contains - value: '[lawyer]' - - type: contains - value: 'DISCLAIMER' - - type: contains - value: 'does not constitute legal advice' - - type: javascript - value: "output.indexOf('DISCLAIMER') > output.indexOf('Sarah Chen')" + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('Sarah Chen') || t.includes('[lawyer]') || !t.includes('DISCLAIMER')) + return { pass: false, score: 0, reason: 'Incomplete' }; + return { pass: true, score: 1, reason: 'Both operations done' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk + - type: javascript + value: file://lib/checks.cjs:traceLog + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.trace) return { pass: true, score: 0.5, reason: 'No trace' }; + const tools = d.trace.flatMap(s => s.toolCalls.map(tc => tc.tool)); + if (tools.length < 3) return { pass: false, score: 0, reason: `Only ${tools.length} calls — expected search + replace + create` }; + return { pass: true, score: 1, reason: `${tools.length} steps: ${tools.join(' → ')}` }; + metric: multi_step # ============================================================================= -# EXPORT -- save edited DOCX to results/output/ +# EXPORT — save edited DOCX # ============================================================================= - description: 'Export: replace + heading + save' vars: fixture: memorandum.docx keepFile: true - task: 'Replace "$25,000,000" with "$75,000,000" and add a heading "APPENDIX A" at the end of the document.' + task: 'Replace "$25,000,000" with "$75,000,000" and add a heading "APPENDIX A" at the end.' assert: - - type: contains - value: '$75,000,000' - - type: not-contains - value: '$25,000,000' - - type: contains - value: '$150,000,000' - - type: contains - value: 'APPENDIX A' - - type: javascript - value: "output.indexOf('APPENDIX A') > output.indexOf('Best regards')" + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (!t.includes('$75,000,000') || !t.includes('APPENDIX A')) + return { pass: false, score: 0, reason: 'Edit incomplete' }; + return { pass: true, score: 1, reason: 'Saved correctly' }; + - type: javascript + value: file://lib/checks.cjs:traceAllOk # ============================================================================= -# ASPIRATIONAL -- may not fully work yet. Failures are expected. +# ASPIRATIONAL # ============================================================================= -- description: 'Aspirational: glossary of defined terms at end' +- description: 'Aspirational: glossary at end' vars: fixture: memorandum.docx - task: 'Generate a glossary of defined terms at the end of the document. Include at least: "Acquiror", "go-shop period", "GAAP". Format as a heading "GLOSSARY" followed by the terms.' + keepFile: true + task: 'Generate a glossary of defined terms at the end. Include "Acquiror", "go-shop period", "GAAP". Heading "GLOSSARY" followed by the terms.' assert: - - type: contains - value: 'GLOSSARY' - type: javascript - value: "output.indexOf('GLOSSARY') > output.indexOf('Best regards')" + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('GLOSSARY')) return { pass: false, score: 0, reason: 'Missing' }; + return { pass: true, score: 1, reason: 'Glossary created' }; + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Aspirational: create table from bullet list' +- description: 'Aspirational: bullet list to table' vars: fixture: document.docx - task: 'Turn this bulleted list into a two-column table. First column has the list items, second column is empty. The table should have a header row with "Feature" and "Notes".' + keepFile: true + task: 'Turn this bulleted list into a two-column table with headers "Feature" and "Notes".' assert: - - type: contains - value: 'Feature' - - type: contains - value: 'Notes' + - type: javascript + value: | + const d = JSON.parse(output); + const t = d.documentText || ''; + if (t.includes('Feature') && t.includes('Notes')) return { pass: true, score: 1, reason: 'Table created' }; + return { pass: false, score: 0, reason: 'Table headers missing' }; + - type: javascript + value: file://lib/checks.cjs:traceLog -- description: 'Aspirational: redline with tracked changes' +- description: 'Aspirational: tracked change redline' vars: fixture: memorandum.docx - task: 'In the "Restrictive Covenants" section, replace "No liquidation" with "Company may not pursue liquidation" using tracked changes so it can be reviewed.' + keepFile: true + task: 'In the "Restrictive Covenants" section, replace "No liquidation" with "Company may not pursue liquidation" using tracked changes.' assert: - - type: contains - value: 'Restrictive Covenants' - - type: contains - value: 'MEMORANDUM' + - type: javascript + value: | + const d = JSON.parse(output); + if (!d.documentText?.includes('MEMORANDUM')) return { pass: false, score: 0, reason: 'Document damaged' }; + return { pass: true, score: 1, reason: 'Document intact' }; + - type: javascript + value: file://lib/checks.cjs:traceLog diff --git a/evals/tests/tool-quality.yaml b/evals/tests/tool-quality.yaml index 647431a5cd..f578865c69 100644 --- a/evals/tests/tool-quality.yaml +++ b/evals/tests/tool-quality.yaml @@ -1,322 +1,264 @@ -# Tool quality tests — does the LLM pick the right tool with valid arguments? +# Tool quality tests — measures how well LLMs understand our tool definitions. # -# Setup: native promptfoo providers with the full public SDK tool bundle: -# superdoc_search, superdoc_get_content, superdoc_edit, superdoc_format, -# superdoc_create, superdoc_list, superdoc_comment, superdoc_track_changes, -# superdoc_mutations +# Constraint: single turn, tool_choice: required. # -# Single turn. tool_choice: required. No multi-step agent loop. -# -# Assertion layers: -# tool-call-f1 — were the correct tools called? (set comparison) -# javascript — were arguments structured correctly? +# Tests are grouped by ASSERTION CONFIDENCE, not by document operation: +# Group 1: DEFINITIVE — one correct answer, strict tool + args validation +# Group 2: FIRST-INSTINCT — which tool does the model reach for? +# Scored: 1.0 = ideal tool, 0.7 = reads first, 0.0 = wrong category +# Group 3: ARGUMENT QUALITY — validates args regardless of which tool # ============================================================================= -# READING — correct tool for finding/reading content +# GROUP 1: DEFINITIVE — single-step tasks with ONE correct answer +# These are the strongest tests. The first call IS the complete action. # ============================================================================= -- description: 'Text search uses superdoc_search with text selector' - metadata: { category: reading } +- description: 'Get full document text' + metadata: { category: definitive, group: 1 } vars: - task: 'Find all paragraphs containing the word "Introduction" in the document.' + task: 'Get the full plain text of this document.' assert: - type: tool-call-f1 - value: [superdoc_search] - threshold: 0.5 # may also call superdoc_get_content + value: [superdoc_get_content] + threshold: 1.0 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:textSearchArgs + value: file://lib/checks.cjs:usesGetContentText metric: argument_accuracy -- description: 'Node search uses superdoc_search with node selector' - metadata: { category: reading } +- description: 'Get document as markdown' + metadata: { category: definitive, group: 1 } vars: - task: 'List all headings in the document.' - expectedNodeType: heading + task: 'Export this document as markdown.' assert: - type: tool-call-f1 - value: [superdoc_search] + value: [superdoc_get_content] threshold: 0.5 metric: tool_selection - - type: javascript - value: file://lib/checks.cjs:nodeSearchArgs - metric: argument_accuracy -- description: 'Get full text calls superdoc_get_content with action text' - metadata: { category: reading } +- description: 'Add heading at end' + metadata: { category: definitive, group: 1 } vars: - task: 'Pull the full plain text from this document so I can use it somewhere else.' + task: 'Add a new heading "Chapter 2" at the end of the document.' + expectedCreateAction: heading assert: - type: tool-call-f1 - value: [superdoc_get_content] - threshold: 1.0 + value: [superdoc_create] + threshold: 0.5 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesGetContentText + value: file://lib/checks.cjs:usesCreateAction metric: argument_accuracy + - type: javascript + value: file://lib/checks.cjs:noTextInsertForStructure + metric: correctness -- description: 'Count occurrences uses superdoc_search' - metadata: { category: reading } +- description: 'Add paragraph at end' + metadata: { category: definitive, group: 1 } vars: - task: 'How many times does "Acme Corp" appear in this document?' + task: 'Add a new paragraph at the end of the document.' + expectedCreateAction: paragraph assert: - type: tool-call-f1 - value: [superdoc_search] - threshold: 0.5 # may also call superdoc_get_content + value: [superdoc_create] + threshold: 0.5 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:textSearchArgs + value: file://lib/checks.cjs:usesCreateAction metric: argument_accuracy -# ============================================================================= -# MUTATIONS — replace, delete, multi-step -# ============================================================================= - -- description: 'Replace uses superdoc_search + superdoc_edit or superdoc_mutations' - metadata: { category: mutation } +- description: 'Undo last change' + metadata: { category: definitive, group: 1 } vars: - task: 'Replace the text "old title" with "new title" in the document.' + task: 'Undo the last change.' assert: - type: tool-call-f1 - value: [superdoc_search, superdoc_edit] - threshold: 0.5 # allows superdoc_mutations instead of superdoc_edit + value: [superdoc_edit] + threshold: 1.0 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesRewriteOp + value: file://lib/checks.cjs:usesEditUndo metric: argument_accuracy -- description: 'Delete uses superdoc_search + superdoc_edit or superdoc_mutations' - metadata: { category: mutation } +- description: 'Text search with pattern' + metadata: { category: definitive, group: 1 } vars: - task: 'Remove the sentence "This is a draft document." from the introduction.' + task: 'Find all paragraphs containing the word "Introduction" in the document.' assert: - type: tool-call-f1 - value: [superdoc_search, superdoc_edit] + value: [superdoc_search] threshold: 0.5 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesDeleteOp + value: file://lib/checks.cjs:textSearchArgs metric: argument_accuracy -- description: 'Multi-step replace uses atomic: true with 2+ steps' - metadata: { category: mutation } +- description: 'Node search for headings' + metadata: { category: definitive, group: 1 } vars: - task: 'Replace "Q1" with "Quarter 1" and "Q2" with "Quarter 2" atomically.' + task: 'Find all headings in the document.' + expectedNodeType: heading assert: - type: tool-call-f1 - value: [superdoc_search, superdoc_mutations] + value: [superdoc_search] threshold: 0.5 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:atomicMultiStep + value: file://lib/checks.cjs:nodeSearchArgs metric: argument_accuracy -- description: 'First-occurrence replace uses require "first" in mutation, not "any"' - metadata: { category: mutation } +- description: 'Count text occurrences' + metadata: { category: definitive, group: 1 } vars: - task: 'Replace the first occurrence of "draft" with "final" in the document.' + task: 'How many times does "Acme Corp" appear in this document?' assert: - type: tool-call-f1 - value: [superdoc_search, superdoc_edit] + value: [superdoc_search] threshold: 0.5 metric: tool_selection - type: javascript - value: file://lib/checks.cjs:noRequireAny - metric: correctness + value: file://lib/checks.cjs:textSearchArgs + metric: argument_accuracy # ============================================================================= -# FORMATTING — bold, highlight, underline via superdoc_format or superdoc_mutations +# GROUP 2: FIRST-INSTINCT — multi-step tasks, scored by tool choice quality +# 1.0 = ideal tool (model knows the right tool), 0.7 = reads first, 0.0 = wrong +# The prompt comparison (full vs minimal) reveals tool description quality. # ============================================================================= -- description: 'Bold uses superdoc_format or superdoc_mutations with correct format args' - metadata: { category: format } +# --- Edit tasks --- + +- description: 'Replace text (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Make the word "Important" bold in the document.' + task: 'Replace the text "old title" with "new title" in the document.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_format] - threshold: 0.5 # may use superdoc_mutations instead - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:correctFormatArgs - metric: argument_accuracy + value: file://lib/checks.cjs:instinctEdit + metric: tool_instinct -- description: 'Rewrite + format must be separate batches in superdoc_mutations' - metadata: { category: format } +- description: 'Delete sentence (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Replace "old text" with "new text" and make it bold.' + task: 'Remove the sentence "This is a draft document." from the introduction.' assert: - type: javascript - value: file://lib/checks.cjs:noMixedBatch - metric: correctness + value: file://lib/checks.cjs:instinctEdit + metric: tool_instinct -- description: 'Highlight uses superdoc_format or superdoc_mutations with format.apply' - metadata: { category: format, source: spreadsheet } +- description: 'Atomic multi-step replace (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Apply yellow highlight to every occurrence of "high risk" in the document.' + task: 'Replace "Q1" with "Quarter 1" and "Q2" with "Quarter 2" atomically.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_format] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:correctFormatArgs - metric: argument_accuracy + value: file://lib/checks.cjs:instinctEdit + metric: tool_instinct -- description: 'Underline uses superdoc_format or superdoc_mutations with format.apply' - metadata: { category: format, source: spreadsheet } +- description: 'First-occurrence replace (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Underline the sentence containing "payment terms" in the document.' + task: 'Replace the first occurrence of "draft" with "final" in the document.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_format] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:correctFormatArgs - metric: argument_accuracy + value: file://lib/checks.cjs:instinctEdit + metric: tool_instinct -# ============================================================================= -# STRUCTURAL CREATION — superdoc_create for headings and paragraphs -# ============================================================================= +# --- Format tasks --- -- description: 'Add heading uses superdoc_create with action heading' - metadata: { category: structure } +- description: 'Bold text (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Add a new heading "Chapter 2" at the end of the document.' - expectedCreateAction: heading + task: 'Make the word "Important" bold in the document.' assert: - - type: tool-call-f1 - value: [superdoc_create] - threshold: 0.5 - metric: tool_selection - - type: javascript - value: file://lib/checks.cjs:usesCreateAction - metric: argument_accuracy - type: javascript - value: file://lib/checks.cjs:noTextInsertForStructure - metric: correctness + value: file://lib/checks.cjs:instinctFormat + metric: tool_instinct -- description: 'Add paragraph uses superdoc_create with action paragraph' - metadata: { category: structure } +- description: 'Highlight text (instinct)' + metadata: { category: instinct, group: 2, source: spreadsheet } vars: - task: 'Add a new paragraph at the end of the document.' - expectedCreateAction: paragraph + task: 'Apply yellow highlight to every occurrence of "high risk" in the document.' assert: - - type: tool-call-f1 - value: [superdoc_create] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesCreateAction - metric: argument_accuracy + value: file://lib/checks.cjs:instinctFormat + metric: tool_instinct -# ============================================================================= -# TABLES — no table creation tool in public surface, model should search first -# ============================================================================= - -- description: 'Aspirational: Insert table searches for target position' - metadata: { category: tables, source: spreadsheet } +- description: 'Underline text (instinct)' + metadata: { category: instinct, group: 2, source: spreadsheet } vars: - task: 'Insert a 4-column by 3-row table under the Pricing Details heading.' + task: 'Underline the sentence containing "payment terms" in the document.' assert: - # Table creation is not in the public surface (superdoc_create only supports - # heading/paragraph). This tests that the model at least searches for context. - - type: tool-call-f1 - value: [superdoc_search] - threshold: 0.5 - metric: tool_selection + - type: javascript + value: file://lib/checks.cjs:instinctFormat + metric: tool_instinct -# ============================================================================= -# COMMENTS — superdoc_comment -# ============================================================================= +# --- Comment tasks --- -- description: 'Add comment uses superdoc_comment with action create' - metadata: { category: comments } +- description: 'Add comment (instinct)' + metadata: { category: instinct, group: 2 } vars: task: 'Add a comment on the first paragraph saying "Needs legal review".' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_comment] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesCommentCreate - metric: argument_accuracy + value: file://lib/checks.cjs:instinctComment + metric: tool_instinct -- description: 'Comment on liability clauses uses superdoc_comment' - metadata: { category: comments, source: spreadsheet } +- description: 'Comment on liability clauses (instinct)' + metadata: { category: instinct, group: 2, source: spreadsheet } vars: - task: 'Add a review comment to each paragraph containing "limitation of liability" in the document.' + task: 'Add a review comment to each paragraph containing "limitation of liability".' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_comment] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesCommentCreate - metric: argument_accuracy + value: file://lib/checks.cjs:instinctComment + metric: tool_instinct -# ============================================================================= -# TRACKED CHANGES — changeMode argument on editing tools -# ============================================================================= +# --- List tasks --- -- description: 'Tracked change sets changeMode to tracked' - metadata: { category: tracked_changes } +- description: 'Convert bullets to numbered list (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Suggest replacing "annual review" with "quarterly review" using tracked changes.' + task: 'Convert the bullet points in section 2 into a numbered list.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_edit] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:isTrackedMode - metric: correctness + value: file://lib/checks.cjs:instinctList + metric: tool_instinct -- description: 'Direct edit does not set tracked mode' - metadata: { category: tracked_changes } +- description: 'Change list numbering style (instinct)' + metadata: { category: instinct, group: 2, source: spreadsheet } vars: - task: 'Replace "draft" with "final version" in the title. Apply directly.' + task: 'Change the third level of this list to lowercase Roman numerals.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_edit] - threshold: 0.5 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:isNotTrackedMode - metric: correctness + value: file://lib/checks.cjs:instinctList + metric: tool_instinct -# ============================================================================= -# LISTS — superdoc_list -# ============================================================================= +# --- Tracked changes tasks --- -- description: 'List conversion uses superdoc_list' - metadata: { category: lists } +- description: 'Tracked change edit (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Convert the bullet points in section 2 into a numbered list.' + task: 'Suggest replacing "annual review" with "quarterly review" using tracked changes.' assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_list] - threshold: 0.5 - metric: tool_selection + - type: javascript + value: file://lib/checks.cjs:instinctTrackChanges + metric: tool_instinct -- description: 'Change list numbering uses superdoc_list' - metadata: { category: lists, source: spreadsheet } +- description: 'Direct edit (instinct)' + metadata: { category: instinct, group: 2 } vars: - task: 'Change the third level of this list to lowercase Roman numerals.' + task: 'Replace "draft" with "final version" in the title. Apply directly.' assert: - - type: tool-call-f1 - value: [superdoc_list] - threshold: 0.5 - metric: tool_selection + - type: javascript + value: file://lib/checks.cjs:instinctEdit + metric: tool_instinct # ============================================================================= -# HYGIENE — argument quality, efficiency +# GROUP 3: ARGUMENT QUALITY — validates args regardless of which tool # ============================================================================= - description: 'No hallucinated doc or sessionId params' - metadata: { category: hygiene } + metadata: { category: quality, group: 3 } vars: task: 'Extract all the plain text so I can paste it elsewhere.' assert: @@ -324,74 +266,20 @@ value: file://lib/checks.cjs:noHallucinatedParams metric: argument_accuracy -- description: 'Undo calls superdoc_edit with action undo' - metadata: { category: history } +- description: 'Rewrite + format must not be mixed in one batch' + metadata: { category: quality, group: 3 } vars: - task: 'Undo that last change, it was wrong.' + task: 'Replace "old text" with "new text" and make it bold.' assert: - - type: tool-call-f1 - value: [superdoc_edit] - threshold: 1.0 - metric: tool_selection - type: javascript - value: file://lib/checks.cjs:usesEditUndo - metric: argument_accuracy + value: file://lib/checks.cjs:noMixedBatch + metric: correctness - description: 'Simple replace uses 5 or fewer tool calls' - metadata: { category: efficiency } + metadata: { category: quality, group: 3 } vars: task: 'Replace the word "hello" with "goodbye" in the document.' assert: - type: javascript metric: efficiency value: 'output.length <= 5' - -# ============================================================================= -# ASPIRATIONAL — features that may require specialized tools. -# ============================================================================= - -- description: 'Aspirational: Insert table of contents' - metadata: { category: aspirational, source: spreadsheet } - vars: - task: 'Insert a table of contents immediately following the title page.' - assert: - - type: tool-call-f1 - value: [superdoc_search] - threshold: 0.5 # model should at least search for the insertion point - metric: tool_selection - -- description: 'Aspirational: Accept formatting tracked changes uses decide action' - metadata: { category: aspirational, source: spreadsheet } - vars: - task: 'Accept all tracked changes that only affect formatting.' - assert: - - type: tool-call-f1 - value: [superdoc_track_changes] - threshold: 0.5 - metric: tool_selection - - type: javascript - value: file://lib/checks.cjs:usesTrackChangesDecide - metric: argument_accuracy - -- description: 'Aspirational: Turn text into hyperlink (not in public surface)' - metadata: { category: aspirational, source: spreadsheet } - vars: - task: 'Turn every instance of "SuperDoc" into a hyperlink to https://www.superdoc.dev.' - assert: - # Hyperlinks are not exposed in the public tool surface (superdoc_format only - # supports inline, set_alignment, set_indentation, set_spacing, set_style). - # The model should at least search for the text to attempt the task. - - type: tool-call-f1 - value: [superdoc_search] - threshold: 0.5 - metric: tool_selection - -- description: 'Aspirational: Identify liability risks reads document' - metadata: { category: aspirational, source: spreadsheet } - vars: - task: 'Which sections of this document pose the greatest liability to me?' - assert: - - type: tool-call-f1 - value: [superdoc_search, superdoc_get_content] - threshold: 0.5 - metric: tool_selection diff --git a/packages/document-api/src/contract/schemas.ts b/packages/document-api/src/contract/schemas.ts index a03fef65ac..107da31f93 100644 --- a/packages/document-api/src/contract/schemas.ts +++ b/packages/document-api/src/contract/schemas.ts @@ -76,8 +76,28 @@ function targetLocatorWithPayload( ): JsonSchema { return { oneOf: [ - objectSchema({ target: ref('SelectionTarget'), ...payloadProperties }, ['target', ...payloadRequired]), - objectSchema({ ref: { type: 'string' }, ...payloadProperties }, ['ref', ...payloadRequired]), + objectSchema( + { + target: { + ...ref('SelectionTarget'), + description: + "Selection target: {kind:'selection', start:{kind:'text', blockId, offset}, end:{kind:'text', blockId, offset}}. Use 'ref' instead when you have a search result handle.", + }, + ...payloadProperties, + }, + ['target', ...payloadRequired], + ), + objectSchema( + { + ref: { + type: 'string', + description: + "Handle ref string from a superdoc_search result. Pass the handle.ref value directly (e.g. 'text:eyJ...'). Preferred over 'target' for inline formatting.", + }, + ...payloadProperties, + }, + ['ref', ...payloadRequired], + ), ], }; } @@ -188,6 +208,8 @@ const SHARED_DEFS: Record = { ['kind', 'nodeType', 'nodeId'], ), SelectionPoint: { + description: + "A point in the document. Use {kind:'text', blockId, offset} for character positions or {kind:'nodeEdge', node:{kind:'block', nodeType, nodeId}, edge:'before'|'after'} for block boundaries.", oneOf: [ objectSchema({ kind: { const: 'text' }, blockId: { type: 'string' }, offset: { type: 'integer', minimum: 0 } }, [ 'kind', @@ -808,19 +830,22 @@ const unknownNodeDiagnosticSchema = objectSchema( const textSelectorSchema = objectSchema( { - type: { const: 'text' }, - pattern: { type: 'string' }, - mode: { enum: ['contains', 'regex'] }, - caseSensitive: { type: 'boolean' }, + type: { const: 'text', description: "Must be 'text' for text pattern search." }, + pattern: { type: 'string', description: 'Text or regex pattern to match.' }, + mode: { enum: ['contains', 'regex'], description: "Match mode: 'contains' (substring) or 'regex'." }, + caseSensitive: { type: 'boolean', description: 'Case-sensitive matching. Default: false.' }, }, ['type', 'pattern'], ); const nodeSelectorSchema = objectSchema( { - type: { const: 'node' }, - nodeType: { enum: [...nodeTypeValues] }, - kind: { enum: ['block', 'inline'] }, + type: { const: 'node', description: "Must be 'node' for node type search." }, + nodeType: { + enum: [...nodeTypeValues], + description: 'Block type to match (paragraph, heading, table, listItem, etc.).', + }, + kind: { enum: ['block', 'inline'], description: "Filter: 'block' or 'inline'." }, }, ['type'], ); @@ -1475,25 +1500,41 @@ const sdFragmentSchema: JsonSchema = { const placementSchema: JsonSchema = { enum: ['before', 'after', 'insideStart', 'insideEnd'] }; -const nestingPolicySchema = objectSchema({ - tables: { enum: ['forbid', 'allow'] }, -}); +const nestingPolicySchema: JsonSchema = { + ...objectSchema({ + tables: { enum: ['forbid', 'allow'] }, + }), + description: "Controls nesting behavior. tables: 'allow' permits inserting tables inside other tables.", +}; const insertInputSchema: JsonSchema = { oneOf: [ objectSchema( { - target: textAddressSchema, - value: { type: 'string' }, - type: { type: 'string', enum: ['text', 'markdown', 'html'] }, + target: { + ...textAddressSchema, + description: "Insertion point: {kind:'text', blockId:'...', range:{start, end}}.", + }, + value: { type: 'string', description: 'Text content to insert.' }, + type: { + type: 'string', + enum: ['text', 'markdown', 'html'], + description: "Content format: 'text' (default), 'markdown', or 'html'.", + }, }, ['value'], ), objectSchema( { - target: blockNodeAddressSchema, - content: sdFragmentSchema, - placement: placementSchema, + target: { + ...blockNodeAddressSchema, + description: "Block address for structural insertion: {kind:'block', nodeType:'...', nodeId:'...'}.", + }, + content: { ...sdFragmentSchema, description: 'Document fragment to insert (structured content).' }, + placement: { + ...placementSchema, + description: "Where to place content relative to target: 'before', 'after', 'insideStart', or 'insideEnd'.", + }, nestingPolicy: nestingPolicySchema, }, ['content'], @@ -2718,7 +2759,10 @@ const operationSchemas: Record = { }, getHtml: { input: objectSchema({ - unflattenLists: { type: 'boolean' }, + unflattenLists: { + type: 'boolean', + description: 'When true, flattens nested list structures in output. Default: false.', + }, }), output: { type: 'string' }, }, @@ -2764,23 +2808,32 @@ const operationSchemas: Record = { oneOf: [ // Text replacement: TargetLocator + text { - ...targetLocatorWithPayload({ text: { type: 'string' } }, ['text']), + ...targetLocatorWithPayload({ text: { type: 'string', description: 'Replacement text content.' } }, ['text']), }, // Structural replacement: exactly one of (target | ref) + content { oneOf: [ objectSchema( { - target: { oneOf: [blockNodeAddressSchema, selectionTargetSchema] }, - content: sdFragmentSchema, + target: { + oneOf: [blockNodeAddressSchema, selectionTargetSchema], + description: 'Target block or selection to replace.', + }, + content: { + ...sdFragmentSchema, + description: 'Document fragment to replace with (structured content).', + }, nestingPolicy: nestingPolicySchema, }, ['target', 'content'], ), objectSchema( { - ref: { type: 'string' }, - content: sdFragmentSchema, + ref: { type: 'string', description: 'Reference handle from a previous search result.' }, + content: { + ...sdFragmentSchema, + description: 'Document fragment to replace with (structured content).', + }, nestingPolicy: nestingPolicySchema, }, ['ref', 'content'], @@ -2795,7 +2848,9 @@ const operationSchemas: Record = { }, delete: { input: { - ...targetLocatorWithPayload({ behavior: deleteBehaviorSchema }), + ...targetLocatorWithPayload({ + behavior: { ...deleteBehaviorSchema, description: "Delete behavior: 'selection' (default) or 'exact'." }, + }), }, output: textMutationResultSchemaFor('delete'), success: textMutationSuccessSchema, @@ -2803,7 +2858,16 @@ const operationSchemas: Record = { }, 'format.apply': { input: { - ...targetLocatorWithPayload({ inline: buildInlineRunPatchSchema() }, ['inline']), + ...targetLocatorWithPayload( + { + inline: { + ...buildInlineRunPatchSchema(), + description: + 'Inline formatting properties to apply. Set a property to apply it, use null to clear it. Example: {bold: true, italic: true} or {bold: null} to remove bold.', + }, + }, + ['inline'], + ), }, output: textMutationResultSchemaFor('format.apply'), success: textMutationSuccessSchema, @@ -2964,10 +3028,18 @@ const operationSchemas: Record = { ...objectSchema( { target: paragraphTargetSchema, - left: { type: 'integer', minimum: 0 }, - right: { type: 'integer', minimum: 0 }, - firstLine: { type: 'integer', minimum: 0 }, - hanging: { type: 'integer', minimum: 0 }, + left: { type: 'integer', minimum: 0, description: 'Left indentation in twips (1440 = 1 inch).' }, + right: { type: 'integer', minimum: 0, description: 'Right indentation in twips (1440 = 1 inch).' }, + firstLine: { + type: 'integer', + minimum: 0, + description: 'First line indent in twips. Cannot be combined with hanging.', + }, + hanging: { + type: 'integer', + minimum: 0, + description: 'Hanging indent in twips. Cannot be combined with firstLine.', + }, }, ['target'], ), @@ -2989,10 +3061,17 @@ const operationSchemas: Record = { ...objectSchema( { target: paragraphTargetSchema, - before: { type: 'integer', minimum: 0 }, - after: { type: 'integer', minimum: 0 }, - line: { type: 'integer', minimum: 1 }, - lineRule: { enum: [...LINE_RULES] }, + before: { type: 'integer', minimum: 0, description: 'Space before paragraph in twips (20 twips = 1pt).' }, + after: { type: 'integer', minimum: 0, description: 'Space after paragraph in twips (20 twips = 1pt).' }, + line: { + type: 'integer', + minimum: 1, + description: 'Line spacing value. Meaning depends on lineRule. Must be provided together with lineRule.', + }, + lineRule: { + enum: [...LINE_RULES], + description: "Line spacing rule. Required when 'line' is set.", + }, }, ['target'], ), @@ -3206,6 +3285,8 @@ const operationSchemas: Record = { 'create.paragraph': { input: objectSchema({ at: { + description: + "Position: {kind:'documentEnd'} to append, {kind:'documentStart'} to prepend, or {kind:'before'|'after', target:{kind:'block', nodeType:'...', nodeId:'...'}} for relative placement.", oneOf: [ objectSchema({ kind: { const: 'documentStart' } }, ['kind']), objectSchema({ kind: { const: 'documentEnd' } }, ['kind']), @@ -3225,7 +3306,7 @@ const operationSchemas: Record = { ), ], }, - text: { type: 'string' }, + text: { type: 'string', description: 'Paragraph text content.' }, }), output: createParagraphResultSchemaFor('create.paragraph'), success: createParagraphSuccessSchema, @@ -3234,8 +3315,10 @@ const operationSchemas: Record = { 'create.heading': { input: objectSchema( { - level: headingLevelSchema, + level: { ...headingLevelSchema, description: 'Heading level (1-6).' }, at: { + description: + "Position: {kind:'documentEnd'} to append, {kind:'documentStart'} to prepend, or {kind:'before'|'after', target:{kind:'block', nodeType:'...', nodeId:'...'}} for relative placement.", oneOf: [ objectSchema({ kind: { const: 'documentStart' } }, ['kind']), objectSchema({ kind: { const: 'documentEnd' } }, ['kind']), @@ -3255,7 +3338,7 @@ const operationSchemas: Record = { ), ], }, - text: { type: 'string' }, + text: { type: 'string', description: 'Heading text content.' }, }, ['level'], ), @@ -3552,9 +3635,16 @@ const operationSchemas: Record = { 'lists.insert': { input: objectSchema( { - target: listItemAddressSchema, - position: listInsertPositionSchema, - text: { type: 'string' }, + target: { + ...listItemAddressSchema, + description: + "The target list item. For 'insert': the item to insert relative to. For 'create' with mode 'fromParagraphs': use nodeType 'paragraph' instead. Format: {kind:'block', nodeType:'listItem', nodeId:''}.", + }, + position: { + ...listInsertPositionSchema, + description: "Required. Insert position relative to target: 'before' or 'after'.", + }, + text: { type: 'string', description: 'Text content for the new list item.' }, }, ['target', 'position'], ), @@ -3566,11 +3656,31 @@ const operationSchemas: Record = { input: { type: 'object', properties: { - mode: { enum: ['empty', 'fromParagraphs'] }, - at: ref('BlockAddress'), - target: ref('BlockAddressOrRange'), - kind: listKindSchema, - level: { type: 'integer', minimum: 0, maximum: 8 }, + mode: { + enum: ['empty', 'fromParagraphs'], + description: + "Required. Creation mode: 'empty' creates a new empty list at the paragraph specified by 'at'; 'fromParagraphs' converts existing paragraph(s) specified by 'target' into list items.", + }, + at: { + ...ref('BlockAddress'), + description: + "Required when mode is 'empty'. The paragraph to create the list at. Format: {kind:'block', nodeType:'paragraph', nodeId:''}.", + }, + target: { + ...ref('BlockAddressOrRange'), + description: + "Required when mode is 'fromParagraphs'. The paragraph(s) to convert into list items. Format: {kind:'block', nodeType:'paragraph', nodeId:''}.", + }, + kind: { + ...listKindSchema, + description: "List type: 'bullet' for bullet points, 'ordered' for numbered lists.", + }, + level: { + type: 'integer', + minimum: 0, + maximum: 8, + description: 'List nesting level (0-8). 0 is the top level.', + }, preset: { enum: [ 'decimal', @@ -3584,6 +3694,7 @@ const operationSchemas: Record = { 'square', 'dash', ], + description: "Predefined list style preset. Overrides 'kind' with a specific numbering or bullet format.", }, style: objectSchema( { @@ -3920,7 +4031,10 @@ const operationSchemas: Record = { { target: listItemAddressSchema, kind: { enum: ['ordered', 'bullet'] }, - continuity: { enum: ['preserve', 'none'] }, + continuity: { + enum: ['preserve', 'none'], + description: "Numbering continuity: 'preserve' keeps numbering; 'none' restarts.", + }, }, ['target', 'kind'], ), @@ -4247,9 +4361,12 @@ const operationSchemas: Record = { 'comments.create': { input: objectSchema( { - text: { type: 'string' }, - target: textAddressSchema, - parentCommentId: { type: 'string' }, + text: { type: 'string', description: 'Comment text content.' }, + target: { + ...textAddressSchema, + description: "Text range to anchor the comment: {kind:'text', blockId:'...', range:{start:N, end:N}}.", + }, + parentCommentId: { type: 'string', description: 'Parent comment ID for creating a threaded reply.' }, }, ['text'], ), @@ -4261,10 +4378,13 @@ const operationSchemas: Record = { input: objectSchema( { commentId: { type: 'string' }, - text: { type: 'string' }, + text: { type: 'string', description: 'Updated comment text.' }, target: textAddressSchema, - status: { enum: ['resolved'] }, - isInternal: { type: 'boolean' }, + status: { enum: ['resolved'], description: "Set comment status. Use 'resolved' to mark as resolved." }, + isInternal: { + type: 'boolean', + description: 'When true, marks the comment as internal (hidden from external collaborators).', + }, }, ['commentId'], ), @@ -4284,17 +4404,23 @@ const operationSchemas: Record = { }, 'comments.list': { input: objectSchema({ - includeResolved: { type: 'boolean' }, - limit: { type: 'integer' }, - offset: { type: 'integer' }, + includeResolved: { + type: 'boolean', + description: 'When true, includes resolved comments in results. Default: false.', + }, + limit: { type: 'integer', description: 'Maximum number of comments to return.' }, + offset: { type: 'integer', description: 'Number of comments to skip for pagination.' }, }), output: commentsListResultSchema, }, 'trackChanges.list': { input: objectSchema({ - limit: { type: 'integer' }, - offset: { type: 'integer' }, - type: { enum: ['insert', 'delete', 'format'] }, + limit: { type: 'integer', description: 'Maximum number of tracked changes to return.' }, + offset: { type: 'integer', description: 'Number of tracked changes to skip for pagination.' }, + type: { + enum: ['insert', 'delete', 'format'], + description: "Filter by change type: 'insert', 'delete', or 'format'.", + }, }), output: trackChangesListResultSchema, }, @@ -4324,13 +4450,31 @@ const operationSchemas: Record = { 'query.match': { input: objectSchema( { - select: { oneOf: [textSelectorSchema, nodeSelectorSchema] }, - within: blockNodeAddressSchema, - require: { enum: ['any', 'first', 'exactlyOne', 'all'] }, - mode: { enum: ['strict', 'candidates'] }, - includeNodes: { type: 'boolean' }, - limit: { type: 'integer', minimum: 1 }, - offset: { type: 'integer', minimum: 0 }, + select: { + description: + "Search selector. Use {type:'text', pattern:'...'} for text search or {type:'node', nodeType:'paragraph'|'heading'|...} for node search.", + oneOf: [textSelectorSchema, nodeSelectorSchema], + }, + within: { + ...blockNodeAddressSchema, + description: "Limit search scope to within a specific block: {kind:'block', nodeType:'...', nodeId:'...'}.", + }, + require: { + enum: ['any', 'first', 'exactlyOne', 'all'], + description: + "Match cardinality: 'any' (all matches), 'first' (only first), 'exactlyOne' (fail if != 1), 'all' (fail if 0).", + }, + mode: { + enum: ['strict', 'candidates'], + description: + "Search mode: 'strict' (default, exact matching) or 'candidates' (returns scored potential matches).", + }, + includeNodes: { + type: 'boolean', + description: 'When true, includes full node data in results. Default: false.', + }, + limit: { type: 'integer', minimum: 1, description: 'Maximum number of matches to return.' }, + offset: { type: 'integer', minimum: 0, description: 'Number of matches to skip for pagination.' }, }, ['select'], ), @@ -4562,10 +4706,26 @@ const operationSchemas: Record = { const mutationsInputSchema = objectSchema( { - expectedRevision: { type: 'string' }, - atomic: { const: true, type: 'boolean' }, - changeMode: { enum: ['direct', 'tracked'] }, - steps: arraySchema(mutationStepSchema), + expectedRevision: { + type: 'string', + description: + 'Document revision for optimistic concurrency. Mutation fails if document was modified since this revision.', + }, + atomic: { + const: true, + type: 'boolean', + description: 'Must be true. All steps execute as one atomic transaction.', + }, + changeMode: { + enum: ['direct', 'tracked'], + description: + "Required. Use 'direct' for immediate edits or 'tracked' for suggestions. Must always be provided.", + }, + steps: { + ...arraySchema(mutationStepSchema), + description: + "Ordered array of mutation steps. Each step needs 'op' (text.rewrite, text.insert, text.delete, format.apply, or assert) and a 'where' targeting clause.", + }, }, ['atomic', 'changeMode', 'steps'], ); diff --git a/packages/sdk/codegen/src/__tests__/contract-integrity.test.ts b/packages/sdk/codegen/src/__tests__/contract-integrity.test.ts index e393392254..14be6fbc2d 100644 --- a/packages/sdk/codegen/src/__tests__/contract-integrity.test.ts +++ b/packages/sdk/codegen/src/__tests__/contract-integrity.test.ts @@ -314,13 +314,13 @@ describe('Tools policy integrity', () => { }); describe('agentVisible param annotation integrity', () => { - const EXPECTED_HIDDEN = new Set(['out']); + const EXPECTED_HIDDEN = new Set(['out', 'expectedRevision']); - test('expected transport-envelope params are agentVisible: false', async () => { + test('expected transport-envelope params are agentVisible: false when present', async () => { const contract = await loadJson(CONTRACT_PATH); for (const [, op] of Object.entries(contract.operations)) { for (const param of op.params) { - if (EXPECTED_HIDDEN.has(param.name)) { + if (EXPECTED_HIDDEN.has(param.name) && 'agentVisible' in param) { expect(param.agentVisible).toBe(false); } } diff --git a/packages/sdk/codegen/src/generate-intent-tools.mjs b/packages/sdk/codegen/src/generate-intent-tools.mjs index c5ee22619d..6e16631f12 100644 --- a/packages/sdk/codegen/src/generate-intent-tools.mjs +++ b/packages/sdk/codegen/src/generate-intent-tools.mjs @@ -44,6 +44,39 @@ function sanitizeSchema(schema) { result.enum = values; } else { result.oneOf = result.oneOf.map(sanitizeSchema); + + // Remove empty-object branches ({}) from oneOf — they represent null/clear + // but are opaque to LLMs. The parent description handles the "use null to clear" guidance. + result.oneOf = result.oneOf.filter( + (branch) => !(typeof branch === 'object' && Object.keys(branch).length === 0), + ); + + // Deduplicate oneOf branches with identical simple types (string, number, boolean). + // Keep the one with the longer description. Don't deduplicate objects (they may have different properties). + const simpleSeen = new Map(); + const deduped = []; + for (const branch of result.oneOf) { + const isSimple = branch.type && branch.type !== 'object' && branch.type !== 'array'; + const key = isSimple ? branch.type : null; + if (key && simpleSeen.has(key)) { + const existing = simpleSeen.get(key); + if ((branch.description || '').length > (existing.description || '').length) { + deduped[deduped.indexOf(existing)] = branch; + simpleSeen.set(key, branch); + } + } else { + if (key) simpleSeen.set(key, branch); + deduped.push(branch); + } + } + result.oneOf = deduped; + + // Collapse oneOf with a single branch + if (result.oneOf.length === 1) { + const only = result.oneOf[0]; + delete result.oneOf; + Object.assign(result, only); + } } } if (Array.isArray(result.anyOf)) { @@ -208,17 +241,23 @@ function buildIntentTools(contract) { }; // Collect all properties across all operations (excluding action). - // A property is marked required only if every operation that defines it - // also marks it required — otherwise it's conditionally required per-action - // and must stay optional in the merged schema. + // Track which actions require each param so we can annotate descriptions. const allProperties = { action: actionProperty }; - /** @type {Map} */ + /** @type {Map} */ const propPresence = new Map(); for (const { operation } of ops) { const opSchema = buildInputSchemaFromParams(operation); const opRequired = new Set(opSchema.required ?? []); + // Also check the contract inputSchema's required array — CLI params may + // strip required flags (e.g. when EXTRA_CLI_PARAMS exist), but the + // contract schema is authoritative for which fields the operation needs. + const contractRequired = operation.inputSchema?.required; + if (Array.isArray(contractRequired)) { + for (const key of contractRequired) opRequired.add(key); + } + for (const [propName, propSchema] of Object.entries(opSchema.properties ?? {})) { if (propName === 'action') continue; @@ -226,17 +265,18 @@ function buildIntentTools(contract) { allProperties[propName] = { ...propSchema }; } - const entry = propPresence.get(propName) ?? { total: 0, requiredCount: 0 }; + const entry = propPresence.get(propName) ?? { total: 0, requiredCount: 0, requiredBy: [] }; entry.total++; - if (opRequired.has(propName)) entry.requiredCount++; + if (opRequired.has(propName)) { + entry.requiredCount++; + entry.requiredBy.push(operation.intentAction); + } propPresence.set(propName, entry); } } // 'action' is always required; other props are required only if they // appear in every operation AND every operation marks them required. - // If a param only exists in some actions, it's conditionally required - // and must stay optional in the merged schema. const opCount = ops.length; const allRequired = ['action']; for (const [propName, { total, requiredCount }] of propPresence) { @@ -245,6 +285,34 @@ function buildIntentTools(contract) { } } + // Annotate descriptions: for params required by some (not all) actions, + // add "Required for action X, Y." so the LLM knows when to include them. + for (const [propName, { requiredCount, requiredBy }] of propPresence) { + if (requiredCount > 0 && requiredCount < opCount && allProperties[propName]) { + const actions = requiredBy.map((a) => `'${a}'`).join(', '); + const existing = allProperties[propName].description || ''; + const suffix = `Required for ${requiredBy.length === 1 ? 'action' : 'actions'} ${actions}.`; + allProperties[propName] = { + ...allProperties[propName], + description: existing ? `${existing} ${suffix}` : suffix, + }; + } + } + + // Add fallback descriptions for complex undescribed params. + for (const [propName, propSchema] of Object.entries(allProperties)) { + if (propSchema.description) continue; + if (propName === 'target') { + allProperties[propName] = { ...propSchema, description: "Target address object. Use 'ref' instead if you have a search handle. Format: {kind:'text', blockId, range:{start,end}} or {kind:'block', nodeType, nodeId}." }; + } else if (propName === 'ref') { + allProperties[propName] = { ...propSchema, description: "Handle ref string from superdoc_search. Pass handle.ref value directly (e.g. 'text:eyJ...'). Preferred for text-level operations." }; + } else if (propName === 'content') { + allProperties[propName] = { ...propSchema, description: "Document fragment content (structured JSON)." }; + } else if (propName === 'inline') { + allProperties[propName] = { ...propSchema, description: "Inline formatting to apply: {bold: true, italic: true, underline: true, ...}." }; + } + } + const inputSchema = { type: 'object', properties: allProperties, diff --git a/packages/sdk/tools/system-prompt.md b/packages/sdk/tools/system-prompt.md index 682427f2d7..8a66b314dd 100644 --- a/packages/sdk/tools/system-prompt.md +++ b/packages/sdk/tools/system-prompt.md @@ -1,5 +1,7 @@ You are a document editing assistant. You have a DOCX document open and a set of intent-based tools available. +**Always take action using tools.** When the user asks you to do something, call the appropriate tool immediately. Do not ask clarifying questions unless the request is truly ambiguous. Make reasonable assumptions (e.g., default heading level 1, append to end if no position specified). + ## Tools overview | Tool | Purpose | @@ -22,7 +24,10 @@ Every editing tool needs a **target** — an address telling the API *where* to Use `superdoc_search` to find content. Each match item returns: -- **`handle`** — an opaque reference for text-level operations. Pass it directly as `target` to `superdoc_edit` and `superdoc_format` (for inline styles like bold, italic, etc.). +- **`handle.ref`** — a ref string for text-level operations. Pass the ref string as: + - `ref` parameter on `superdoc_format` (for inline styles like bold, italic) + - `ref` parameter on `superdoc_edit` (for text replacement, deletion) + - Example: `superdoc_format({action: "inline", ref: "text:eyJ...", inline: {bold: true}})` - **`address`** — a block-level address like `{ "kind": "block", "nodeType": "paragraph", "nodeId": "abc123" }`. Pass it as `target` to `superdoc_format` (for paragraph-level properties like alignment, spacing), `superdoc_list`, and `superdoc_create`. ### Text search results @@ -51,7 +56,17 @@ Single-action tools like `superdoc_search` do not require an `action` parameter. 1. **Read first**: Use `superdoc_get_content` to understand the document. 2. **Search before editing**: Use `superdoc_search` to get valid targets. 3. **Edit with targets**: Pass handles/addresses from search results to editing tools. -4. **Batch when possible**: For multi-step edits (e.g., find-and-replace-all, rewrite + restyle), prefer `superdoc_mutations` — it's atomic, faster, and avoids stale-target issues. +4. **Batch when possible**: For multi-step edits (e.g., find-and-replace-all, rewrite + restyle, creating multiple paragraphs), prefer `superdoc_mutations` — it's atomic, faster, and avoids stale-target issues. + +### Placing content near specific text + +To add content near a heading or specific text (e.g., "add a paragraph after the Introduction section"): + +1. **Search for the text**: `superdoc_search({select: {type: "text", pattern: "Introduction"}, require: "first"})` +2. **Get the blockId** from `result.items[0].blocks[0].blockId` +3. **Create content after it**: `superdoc_create({action: "paragraph", text: "...", at: {kind: "after", target: {kind: "block", nodeType: "heading", nodeId: ""}}})` + +**Do NOT search by node type and then try to match by position** — this is unreliable in large documents. Always search for the actual text content to find the exact location. ## Using superdoc_mutations @@ -90,5 +105,9 @@ To resolve a comment, use `action: "update"` with `{ commentId: "", status: ## Important rules - **Do NOT combine `limit`/`offset` with `require: "first"` or `require: "exactlyOne"`** in superdoc_search. Use `require: "any"` with `limit` for paginated results. +- **superdoc_search `select.type`** must be `"text"` or `"node"`. To find headings, use `{type: "node", nodeType: "heading"}`, NOT `{type: "heading"}`. - For `superdoc_format` inline properties, use `null` inside the `inline` object to clear a property (e.g., `"inline": { "bold": null }` removes bold). -- For `superdoc_list` create action: this converts existing paragraphs into list items. Create the paragraph first with `superdoc_create`, then convert it with `superdoc_list` action `create`. +- **Creating lists** requires two modes: + - `mode: "fromParagraphs"` — converts existing paragraphs into list items. Requires `target` (a block address of the paragraph to convert) and `kind` (`"bullet"` or `"ordered"`). + - `mode: "empty"` — creates a new empty list at a paragraph position. Requires `at` (a block address: `{kind:"block", nodeType:"paragraph", nodeId:""}`) and `kind`. + - **Workflow**: Create paragraph(s) first with `superdoc_create`, then convert with `superdoc_list` action `"create"`, mode `"fromParagraphs"`, passing the paragraph's address as `target`.