@stll/folio-agents 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -0
- package/dist/bridge.d.ts +36 -0
- package/dist/bridge.js +0 -0
- package/dist/bridges/editor-ref.d.ts +90 -0
- package/dist/bridges/editor-ref.js +139 -0
- package/dist/bridges/reviewer.d.ts +19 -0
- package/dist/bridges/reviewer.js +48 -0
- package/dist/bridges/shared.d.ts +16 -0
- package/dist/bridges/shared.js +19 -0
- package/dist/compare.d.ts +53 -0
- package/dist/compare.js +285 -0
- package/dist/execute.d.ts +19 -0
- package/dist/execute.js +188 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/parse.d.ts +45 -0
- package/dist/parse.js +157 -0
- package/dist/providers.d.ts +24 -0
- package/dist/providers.js +18 -0
- package/dist/tools.d.ts +16 -0
- package/dist/tools.js +224 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.js +23 -0
- package/package.json +61 -1
- package/skills/folio-agents/SKILL.md +145 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: folio-agents
|
|
3
|
+
description: >-
|
|
4
|
+
Integrate and use @stll/folio-agents, the framework-neutral LLM tool layer
|
|
5
|
+
over @stll/folio-core: function-calling tools that read and mutate a .docx
|
|
6
|
+
document, with every mutation landing as a tracked change or comment
|
|
7
|
+
pending human review. Load this skill when wiring folio's tools into an
|
|
8
|
+
agent's tool-use loop (TanStack AI, Vercel AI SDK, raw Anthropic/OpenAI
|
|
9
|
+
SDKs, or a custom loop), choosing between the headless reviewer bridge and
|
|
10
|
+
the live-editor-ref bridge, or summarizing what changed between two
|
|
11
|
+
document versions.
|
|
12
|
+
metadata:
|
|
13
|
+
type: core
|
|
14
|
+
library: "@stll/folio-agents"
|
|
15
|
+
library_version: "0.0.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# @stll/folio-agents
|
|
19
|
+
|
|
20
|
+
Framework-neutral LLM tool layer over `@stll/folio-core`'s ai-edits engine.
|
|
21
|
+
Gives a model function-calling tools to read and mutate a `.docx` document;
|
|
22
|
+
every mutation (`add_comment`, `suggest_changes`) lands as a tracked change or
|
|
23
|
+
comment pending human review — nothing is silently finalized.
|
|
24
|
+
|
|
25
|
+
## Integration recipe
|
|
26
|
+
|
|
27
|
+
1. `getFolioToolDefinitions()` returns `FolioAgentToolDefinition[]` — plain
|
|
28
|
+
`{ name, description, inputSchema }` objects with a conservative JSON
|
|
29
|
+
Schema subset (`type: "object"`, `properties`, `required`, `enum`,
|
|
30
|
+
`additionalProperties: false`, plain arrays — safe for providers that
|
|
31
|
+
reject uncommon keywords, e.g. Gemini's OpenAPI-3.0 subset).
|
|
32
|
+
2. Register them with your framework:
|
|
33
|
+
- TanStack AI: pass `inputSchema` directly as `toolDefinition({ inputSchema })` — no wrapper needed.
|
|
34
|
+
- Raw Anthropic/OpenAI SDKs: `toAnthropicTools(defs)` / `toOpenAITools(defs)`.
|
|
35
|
+
- Vercel AI SDK: wrap each `inputSchema` in `jsonSchema()` from `ai`.
|
|
36
|
+
3. In your tool-use loop, for every `tool_use` / `tool_call` the model emits:
|
|
37
|
+
call `executeFolioToolCall(name, args, bridge)` → returns
|
|
38
|
+
`{ ok: true, result } | { ok: false, error }`.
|
|
39
|
+
4. Feed that result straight back to the model as the tool result message —
|
|
40
|
+
both branches are meant to reach the model, not just the `ok: true` one:
|
|
41
|
+
error strings are plain language ("blockId not found; re-read...") that
|
|
42
|
+
the model can act on directly.
|
|
43
|
+
|
|
44
|
+
## Bridge selection
|
|
45
|
+
|
|
46
|
+
A `FolioAgentBridge` is the structural seam `executeFolioToolCall` drives.
|
|
47
|
+
Two are shipped:
|
|
48
|
+
|
|
49
|
+
- `createReviewerBridge(FolioDocxReviewer.fromBuffer(...))` — server/headless,
|
|
50
|
+
full capability. Use for a document that isn't open in a live editor (a
|
|
51
|
+
backend job, a batch review pass). Defaults to `mode: "tracked-changes"`
|
|
52
|
+
(pass `{ mode: "direct" }` to edit in place instead — rarely what you want
|
|
53
|
+
for an unreviewed agent).
|
|
54
|
+
- `createEditorRefBridge({ ref, author, getComments, setComments })` — drives
|
|
55
|
+
a live `DocxEditorRef` from `@stll/folio-react` (or anything structurally
|
|
56
|
+
matching `FolioAgentEditorRefLike`). Comments live in host React state, not
|
|
57
|
+
on the ref, hence the `getComments` / `setComments` pair (the same ones the
|
|
58
|
+
host already passes to `DocxEditor`).
|
|
59
|
+
|
|
60
|
+
The editor-ref bridge's capability now depends on what the underlying
|
|
61
|
+
`DocxEditorRef` implements. Against a ref with the read surface
|
|
62
|
+
(`getTrackedChanges`, `getCommentAnchors`, `getSelectionText`, `getPageText`),
|
|
63
|
+
it has full parity with the headless one: `read_changes` returns real tracked
|
|
64
|
+
changes, comment entries carry a resolved `blockId` / `quote`, and `read_page`
|
|
65
|
+
/ `read_selection` work against the live view. Against an older ref that
|
|
66
|
+
predates those methods, each falls back independently: `read_changes` returns
|
|
67
|
+
`[]`, comment entries keep `blockId: null` / `quote: ""`, and `read_page` /
|
|
68
|
+
`read_selection` report an unsupported-capability error (see
|
|
69
|
+
`src/bridges/editor-ref.ts` for the exact per-method fallback). A bridge
|
|
70
|
+
omits an optional capability member entirely rather than implementing it as a
|
|
71
|
+
no-op, and `executeFolioToolCall` reports the corresponding tool call as
|
|
72
|
+
unsupported rather than throwing — this is how the headless reviewer bridge
|
|
73
|
+
signals `read_page` / `read_selection` / `scroll_to_block` as unsupported too,
|
|
74
|
+
since a headless document has no live page/selection/scroll surface at all.
|
|
75
|
+
|
|
76
|
+
## Host-managed review queue
|
|
77
|
+
|
|
78
|
+
A host that already has its own review-queue UX — its own place to store a
|
|
79
|
+
model's proposed edits pending approval, separate from folio's
|
|
80
|
+
tracked-changes redlines — can skip `executeFolioToolCall` for
|
|
81
|
+
`suggest_changes` / `add_comment` and instead validate the model's tool-call
|
|
82
|
+
arguments directly with `parseSuggestChangesInput` / `parseAddCommentInput`.
|
|
83
|
+
These are the exact same validation rules `executeFolioToolCall` runs
|
|
84
|
+
(argument shape, the 50-operation cap, the 100,000-character text caps),
|
|
85
|
+
factored out so both the host's path and folio's own executor agree on what a
|
|
86
|
+
valid `suggest_changes` / `add_comment` call looks like. On success they
|
|
87
|
+
return the parsed `FolioAIEditOperation`(s) to route into the host's own
|
|
88
|
+
queue; on failure they return the same plain-language `error` string meant to
|
|
89
|
+
go straight back to the model.
|
|
90
|
+
|
|
91
|
+
## Ground rules for the model
|
|
92
|
+
|
|
93
|
+
- Block ids (`blockId`) and comment ids (`commentId`) only ever come from a
|
|
94
|
+
prior tool call in the same conversation — `read_document`, `find_text`, or
|
|
95
|
+
`read_comments`. Never invent or reuse one from outside the conversation;
|
|
96
|
+
ids are not guessable and change whenever the document's structure changes.
|
|
97
|
+
- `suggest_changes` operations that get skipped (`skipped: [{ id, reason }]`)
|
|
98
|
+
return a plain-language reason, not a machine code (e.g. "the block changed
|
|
99
|
+
since your snapshot; re-read the document and retry with fresh ids"). Treat
|
|
100
|
+
a skip as a retry signal: re-read (`read_document` or `find_text`) and
|
|
101
|
+
reissue with fresh ids, not a dead end.
|
|
102
|
+
- `suggest_changes` defaults to tracked-changes mode on both shipped bridges
|
|
103
|
+
— it proposes redlines for a human to accept or reject, never edits the
|
|
104
|
+
visible text directly.
|
|
105
|
+
- The tool surface here is fixed: five edit operation kinds
|
|
106
|
+
(`replaceInBlock`, `insertAfterBlock`, `insertBeforeBlock`, `replaceBlock`,
|
|
107
|
+
`deleteBlock`) plus comment/reply/resolve. There is no
|
|
108
|
+
`insertSignatureTable`-style structural op, and none should be invented —
|
|
109
|
+
compose the five primitives, or extend `@stll/folio-core`'s ai-edits engine
|
|
110
|
+
itself if a document needs a structural operation this package doesn't
|
|
111
|
+
expose.
|
|
112
|
+
- **Untrusted documents:** `read_document`, `read_comments`, `read_changes`,
|
|
113
|
+
and `find_text` return document content verbatim, so a `.docx` from an
|
|
114
|
+
untrusted party can inject prompt instructions straight into the model's
|
|
115
|
+
context via its text — hosts should treat any document-derived tool result
|
|
116
|
+
as untrusted model input. Mutations stay safe regardless: `suggest_changes`
|
|
117
|
+
and `add_comment` only ever produce tracked changes or comments pending
|
|
118
|
+
human review, so an injected instruction can propose an edit, never apply
|
|
119
|
+
one.
|
|
120
|
+
|
|
121
|
+
## Summarizing what changed
|
|
122
|
+
|
|
123
|
+
Two different questions, two different tools:
|
|
124
|
+
|
|
125
|
+
- **Pending redlines in one document** — what a reviewer would see as tracked
|
|
126
|
+
changes right now: use the `read_changes` tool (headless bridge only; see
|
|
127
|
+
the capability gap above) or call `reviewer.getChanges()` directly.
|
|
128
|
+
- **Between two saved `.docx` versions** — `compareDocxVersions(previousBuffer,
|
|
129
|
+
currentBuffer)` + `formatVersionDiffForLLM(diff)`. Both are plain async
|
|
130
|
+
functions, not tool definitions: a model can't attach two buffers to one
|
|
131
|
+
tool call (they aren't JSON-serializable arguments). Wrap them in a
|
|
132
|
+
host-side tool keyed by version identifiers instead — the model calls it
|
|
133
|
+
with two version ids, your backend resolves the buffers and returns the
|
|
134
|
+
formatted diff text.
|
|
135
|
+
|
|
136
|
+
`compareDocxVersions` compares the AS-ACCEPTED view of each buffer: any
|
|
137
|
+
tracked changes already pending in either one count as already applied before
|
|
138
|
+
the diff runs, so diffing two versions that each carry their own uncommitted
|
|
139
|
+
redlines still produces a clean diff instead of raw markup noise.
|
|
140
|
+
|
|
141
|
+
## Reference
|
|
142
|
+
|
|
143
|
+
Tool names, full JSON Schemas, and the headless/live-editor quickstarts are
|
|
144
|
+
in this package's README.md. Read `src/tools.ts` for the exact input schema
|
|
145
|
+
of every tool and `src/execute.ts` for the skip-reason vocabulary.
|