@stll/folio-agents
Advanced tools
+142
-3
@@ -123,3 +123,3 @@ import { FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, parseFolioDocumentOperationBatch } from "@stll/folio-core/server"; | ||
| * JSON Schema (draft-07 compatible) for ONE document operation: the full | ||
| * ten-variant union accepted by `parseFolioDocumentOperationBatch` in | ||
| * thirteen-variant union accepted by `parseFolioDocumentOperationBatch` in | ||
| * `@stll/folio-core`, one `oneOf` variant per entry in | ||
@@ -209,3 +209,3 @@ * `FOLIO_DOCUMENT_OPERATION_TYPES`. Intended for LLM tool definitions and | ||
| type: "object", | ||
| description: "Apply inline formatting to the text covered by a range handle. Direct mode only (formatting is not representable as a tracked change).", | ||
| description: "Apply inline formatting to the text covered by a range handle in direct or tracked mode.", | ||
| properties: { | ||
@@ -432,2 +432,141 @@ ...operationMetaProperties, | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Insert a row next to the row containing the anchor block.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["insertTableRow"] | ||
| }, | ||
| blockId: blockIdProperty, | ||
| position: { | ||
| type: "string", | ||
| enum: ["after", "before"], | ||
| description: "Insert after the anchor row (default) or before it. Defaults to \"after\"." | ||
| }, | ||
| cellTexts: { | ||
| type: "array", | ||
| description: "Initial text for physical cells in source order; omitted cells stay empty.", | ||
| items: { type: "string" } | ||
| } | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Delete the row containing the anchor block.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["deleteTableRow"] | ||
| }, | ||
| blockId: blockIdProperty | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Insert a column next to the column containing the anchor block.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["insertTableColumn"] | ||
| }, | ||
| blockId: blockIdProperty, | ||
| position: { | ||
| type: "string", | ||
| enum: ["after", "before"], | ||
| description: "Insert after the anchor column (default) or before it. Defaults to \"after\"." | ||
| }, | ||
| cellTexts: { | ||
| type: "array", | ||
| description: "Initial text for newly created physical cells in row order.", | ||
| items: { type: "string" } | ||
| } | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Delete the column containing the anchor block.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["deleteTableColumn"] | ||
| }, | ||
| blockId: blockIdProperty | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Merge a region targeted by an opposite-cell anchor or a downward row count. Tracked mode supports vertical-only regions with empty continuation cells.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["mergeTableCells"] | ||
| }, | ||
| blockId: blockIdProperty, | ||
| endBlockId: { | ||
| type: "string", | ||
| minLength: 1, | ||
| description: "Stable paragraph anchor inside the opposite corner cell." | ||
| }, | ||
| rowCount: { | ||
| type: "integer", | ||
| minimum: 2, | ||
| description: "Number of grid rows to merge downward from the anchored cell." | ||
| } | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| oneOf: [{ required: ["endBlockId"] }, { required: ["rowCount"] }], | ||
| additionalProperties: false | ||
| }, | ||
| { | ||
| type: "object", | ||
| description: "Split one spanned table cell into individual cells. Tracked mode supports vertical-only spans.", | ||
| properties: { | ||
| ...operationMetaProperties, | ||
| type: { | ||
| type: "string", | ||
| enum: ["splitTableCell"] | ||
| }, | ||
| blockId: blockIdProperty | ||
| }, | ||
| required: [ | ||
| "id", | ||
| "type", | ||
| "blockId" | ||
| ], | ||
| additionalProperties: false | ||
| } | ||
@@ -461,3 +600,3 @@ ] | ||
| enum: FOLIO_DOCUMENT_OPERATION_MODES, | ||
| description: "How edits land: \"tracked-changes\" (default) proposes revisions for human review, \"direct\" applies immediately. `formatRange` and `insertSignatureTable` support \"direct\" only." | ||
| description: "How edits land: \"tracked-changes\" (default) proposes revisions for human review, \"direct\" applies immediately. `insertSignatureTable` supports \"direct\" only. Tracked cell merge and split operations support unambiguous vertical-only topology." | ||
| }, | ||
@@ -464,0 +603,0 @@ atomic: { |
+68
-3
@@ -14,2 +14,3 @@ //#region src/parse.ts | ||
| const MAX_OPERATION_TEXT_LENGTH = 1e5; | ||
| const MAX_TABLE_INSERTION_CELL_TEXTS = 256; | ||
| /** Plain-language error for a string argument over {@link MAX_OPERATION_TEXT_LENGTH}. */ | ||
@@ -61,4 +62,4 @@ const explainTextTooLong = (label, length) => `${label} is ${length.toLocaleString()} characters, over the ${MAX_OPERATION_TEXT_LENGTH.toLocaleString()}-character limit; shorten it or split it into multiple operations.`; | ||
| }; | ||
| const OPERATION_TYPES = "replaceInBlock, replaceRange, commentOnRange, insertAfterBlock, insertBeforeBlock, replaceBlock, deleteBlock"; | ||
| const isOperationType = (value) => value === "replaceInBlock" || value === "replaceRange" || value === "commentOnRange" || value === "insertAfterBlock" || value === "insertBeforeBlock" || value === "replaceBlock" || value === "deleteBlock"; | ||
| const OPERATION_TYPES = "replaceInBlock, replaceRange, commentOnRange, formatRange, insertAfterBlock, insertBeforeBlock, replaceBlock, deleteBlock, insertTableRow, deleteTableRow, insertTableColumn, deleteTableColumn, mergeTableCells, splitTableCell"; | ||
| const isOperationType = (value) => value === "replaceInBlock" || value === "replaceRange" || value === "commentOnRange" || value === "formatRange" || value === "insertAfterBlock" || value === "insertBeforeBlock" || value === "replaceBlock" || value === "deleteBlock" || value === "insertTableRow" || value === "deleteTableRow" || value === "insertTableColumn" || value === "deleteTableColumn" || value === "mergeTableCells" || value === "splitTableCell"; | ||
| const readTextRange = (value, index) => { | ||
@@ -100,3 +101,3 @@ const path = `operations[${index}].range`; | ||
| const commentField = typeof comment === "string" ? { comment: { text: comment } } : {}; | ||
| if (type === "replaceRange" || type === "commentOnRange") { | ||
| if (type === "replaceRange" || type === "commentOnRange" || type === "formatRange") { | ||
| const range = readTextRange(raw["range"], index); | ||
@@ -113,2 +114,24 @@ if (typeof range === "string") return range; | ||
| } | ||
| if (type === "formatRange") { | ||
| const rawFormatting = raw["formatting"]; | ||
| if (!isPlainObject(rawFormatting)) return `operations[${index}] (formatRange) requires a \`formatting\` object.`; | ||
| const formatting = {}; | ||
| for (const key of [ | ||
| "bold", | ||
| "italic", | ||
| "underline" | ||
| ]) { | ||
| const value = rawFormatting[key]; | ||
| if (value === void 0) continue; | ||
| if (typeof value !== "boolean") return `operations[${index}].formatting.${key} must be a boolean when provided.`; | ||
| formatting[key] = value; | ||
| } | ||
| if (Object.keys(formatting).length === 0) return `operations[${index}] (formatRange) requires at least one formatting property.`; | ||
| return { | ||
| id: opId, | ||
| type, | ||
| range, | ||
| formatting | ||
| }; | ||
| } | ||
| const replace = raw["replace"]; | ||
@@ -126,2 +149,44 @@ if (typeof replace !== "string") return `operations[${index}] (replaceRange) requires a string \`replace\`.`; | ||
| if (!isNonEmptyString(blockId)) return `operations[${index}].blockId must be a non-empty string.`; | ||
| if (type === "insertTableRow" || type === "insertTableColumn") { | ||
| const position = raw["position"]; | ||
| if (position !== void 0 && position !== "after" && position !== "before") return `operations[${index}] (${type}) \`position\` must be "after" or "before" when provided.`; | ||
| const rawCellTexts = raw["cellTexts"]; | ||
| if (rawCellTexts !== void 0 && !Array.isArray(rawCellTexts)) return `operations[${index}] (${type}) \`cellTexts\` must be an array of strings when provided.`; | ||
| if (rawCellTexts !== void 0 && rawCellTexts.length > MAX_TABLE_INSERTION_CELL_TEXTS) return `operations[${index}] (${type}) \`cellTexts\` has ${rawCellTexts.length.toLocaleString()} entries, over the ${MAX_TABLE_INSERTION_CELL_TEXTS.toLocaleString()}-cell limit.`; | ||
| const cellTexts = []; | ||
| for (const [cellIndex, cellText] of (rawCellTexts ?? []).entries()) { | ||
| if (typeof cellText !== "string") return `operations[${index}] (${type}) \`cellTexts[${cellIndex}]\` must be a string.`; | ||
| if (cellText.length > 1e5) return explainTextTooLong(`operations[${index}] (${type}) \`cellTexts[${cellIndex}]\``, cellText.length); | ||
| cellTexts.push(cellText); | ||
| } | ||
| return { | ||
| id: opId, | ||
| type, | ||
| blockId, | ||
| ...position !== void 0 && { position }, | ||
| ...rawCellTexts !== void 0 && { cellTexts } | ||
| }; | ||
| } | ||
| if (type === "deleteTableRow" || type === "deleteTableColumn" || type === "splitTableCell") return { | ||
| id: opId, | ||
| type, | ||
| blockId | ||
| }; | ||
| if (type === "mergeTableCells") { | ||
| const endBlockId = raw["endBlockId"]; | ||
| const rowCount = raw["rowCount"]; | ||
| if (isNonEmptyString(endBlockId) && rowCount === void 0) return { | ||
| id: opId, | ||
| type, | ||
| blockId, | ||
| endBlockId | ||
| }; | ||
| if (endBlockId === void 0 && typeof rowCount === "number" && Number.isInteger(rowCount) && rowCount >= 2) return { | ||
| id: opId, | ||
| type, | ||
| blockId, | ||
| rowCount | ||
| }; | ||
| return `operations[${index}] (mergeTableCells) requires exactly one non-empty \`endBlockId\` or integer \`rowCount\` of at least 2.`; | ||
| } | ||
| if (type === "replaceInBlock") { | ||
@@ -128,0 +193,0 @@ const find = raw["find"]; |
+39
-12
@@ -86,5 +86,4 @@ import { FOLIO_AGENT_TOOL_NAMES } from "./types.js"; | ||
| * (see `FOLIO_DOCUMENT_OPERATION_JSON_SCHEMA` in `operation-schema.ts`): | ||
| * - excluded types: `formatRange` and `insertSignatureTable` (direct-only, | ||
| * not representable as tracked changes for human review) and | ||
| * `commentOnBlock` (covered by the dedicated `add_comment` tool); | ||
| * - excluded types: `insertSignatureTable` (direct-only) and `commentOnBlock` | ||
| * (covered by the dedicated `add_comment` tool); | ||
| * - `id` is optional here (auto-generated `op-1`, `op-2`, … by `parse.ts`) | ||
@@ -96,10 +95,6 @@ * where the contract requires it; | ||
| * insert/replace formatting knobs (`inheritFormatting`, `pageBreakBefore`, | ||
| * `preserveFormatting`, `styleId`, `position`, `parties`, `quote`, | ||
| * `formatting`) are not exposed to the model. | ||
| * `preserveFormatting`, `styleId`, `parties`, `quote`, | ||
| * except `formatting`) are not exposed to the model. | ||
| */ | ||
| const SUGGEST_CHANGES_EXCLUDED_OPERATION_TYPES = /* @__PURE__ */ new Set([ | ||
| "formatRange", | ||
| "commentOnBlock", | ||
| "insertSignatureTable" | ||
| ]); | ||
| const SUGGEST_CHANGES_EXCLUDED_OPERATION_TYPES = /* @__PURE__ */ new Set(["commentOnBlock", "insertSignatureTable"]); | ||
| /** | ||
@@ -129,5 +124,15 @@ * The contract operation types `suggest_changes` exposes to the model, | ||
| }, | ||
| endBlockId: { | ||
| type: "string", | ||
| minLength: 1, | ||
| description: "For cell merging, a block in the opposite corner cell." | ||
| }, | ||
| rowCount: { | ||
| type: "integer", | ||
| minimum: 2, | ||
| description: "For vertical cell merging, the number of grid rows to merge downward." | ||
| }, | ||
| range: { | ||
| ...FOLIO_TEXT_RANGE_JSON_SCHEMA, | ||
| description: "Required for `replaceRange` and `commentOnRange`: copy the range object returned by `find_text`." | ||
| description: "Required for `replaceRange`, `commentOnRange`, and `formatRange`: copy the range object returned by `find_text`." | ||
| }, | ||
@@ -146,2 +151,24 @@ find: { | ||
| }, | ||
| position: { | ||
| type: "string", | ||
| enum: ["after", "before"], | ||
| description: "For row or column insertion, place the new structure after the anchor or before it." | ||
| }, | ||
| cellTexts: { | ||
| type: "array", | ||
| description: "For row or column insertion, initial text for new physical cells in source order, up to 100,000 characters per cell.", | ||
| maxItems: 256, | ||
| items: { type: "string" } | ||
| }, | ||
| formatting: { | ||
| type: "object", | ||
| description: "Required for `formatRange`: set one or more inline properties to enable or disable.", | ||
| properties: { | ||
| bold: { type: "boolean" }, | ||
| italic: { type: "boolean" }, | ||
| underline: { type: "boolean" } | ||
| }, | ||
| minProperties: 1, | ||
| additionalProperties: false | ||
| }, | ||
| comment: { | ||
@@ -296,3 +323,3 @@ type: "string", | ||
| name: FOLIO_AGENT_TOOL_NAMES.readChanges, | ||
| description: "Read pending tracked changes (insertions and deletions) awaiting human review. Use this to see the effect of edits already suggested via `suggest_changes` before proposing more.", | ||
| description: "Read pending tracked changes awaiting human review. Use this to see the effect of edits already suggested via `suggest_changes` before proposing more.", | ||
| inputSchema: { | ||
@@ -299,0 +326,0 @@ type: "object", |
+3
-3
@@ -1,2 +0,2 @@ | ||
| import { FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FolioAITextRangeHandle, FolioDocumentOperationIssue, FolioDocumentOperationReceipt, FolioDocumentOutlineEntry, FolioDocumentSectionHandle, FolioDocumentStoryHandle } from "@stll/folio-core/server"; | ||
| import { FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FolioAITextRangeHandle, FolioDocumentOperationIssue, FolioDocumentOperationReceipt, FolioDocumentOutlineEntry, FolioDocumentSectionHandle, FolioDocumentStoryHandle, FolioReviewChange } from "@stll/folio-core/server"; | ||
@@ -139,6 +139,6 @@ //#region src/types.d.ts | ||
| }; | ||
| /** A pending tracked change (insertion or deletion), shaped to match {@link FolioDocxReviewer.getChanges}. */ | ||
| /** A pending tracked change, shaped to match {@link FolioDocxReviewer.getChanges}. */ | ||
| type FolioAgentChange = { | ||
| id: string; | ||
| type: "insertion" | "deletion"; | ||
| type: FolioReviewChange["type"]; | ||
| author: string; | ||
@@ -145,0 +145,0 @@ text: string; |
+1
-1
| { | ||
| "name": "@stll/folio-agents", | ||
| "version": "0.6.1", | ||
| "version": "0.7.0", | ||
| "description": "Framework-neutral LLM tool layer over folio's ai-edits engine: function-calling tools so an agent can read and mutate .docx documents through @stll/folio-core.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
Sorry, the diff of this file is too big to display
215615
16.27%4812
20%