@superdoc/sdk 2.26.0 → 2.27.0-next.13
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 +236 -29
- package/dist/agent/actions.cjs +62 -24
- package/dist/agent/actions.js +62 -24
- package/dist/agent/catalog.d.ts +0 -4
- package/dist/agent/catalog.js +0 -4
- package/dist/embedded-prompts.generated.cjs +1 -1
- package/dist/embedded-prompts.generated.js +1 -1
- package/dist/embedded-tools.generated.cjs +5 -5
- package/dist/embedded-tools.generated.js +5 -5
- package/dist/generated/client.cjs +18 -1
- package/dist/generated/client.d.ts +3248 -1019
- package/dist/generated/client.js +18 -1
- package/dist/generated/contract.cjs +1 -2
- package/dist/generated/contract.d.ts +1 -0
- package/dist/generated/contract.js +1 -2
- package/dist/index.cjs +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/presets/full.cjs +4 -0
- package/dist/presets/full.js +4 -0
- package/dist/prompts/full-addendum.md +1 -0
- package/dist/runtime/host.cjs +36 -3
- package/dist/runtime/host.d.ts +5 -0
- package/dist/runtime/host.js +36 -4
- package/dist/runtime/notifications.cjs +124 -0
- package/dist/runtime/notifications.d.ts +35 -0
- package/dist/runtime/notifications.js +116 -0
- package/dist/runtime/process.cjs +4 -1
- package/dist/runtime/process.d.ts +2 -0
- package/dist/runtime/process.js +4 -1
- package/dist/runtime/sdk-policy.d.ts +1 -0
- package/dist/runtime/sdk-version.generated.cjs +1 -1
- package/dist/runtime/sdk-version.generated.d.ts +1 -1
- package/dist/runtime/sdk-version.generated.js +1 -1
- package/dist/runtime/transport-common.cjs +9 -1
- package/dist/runtime/transport-common.d.ts +3 -0
- package/dist/runtime/transport-common.js +8 -1
- package/package.json +7 -7
- package/tools/catalog.json +42 -2
- package/tools/generation-receipt.json +60 -22
- package/tools/tools-policy.json +1 -1
- package/tools/tools.anthropic.json +42 -2
- package/tools/tools.generic.json +42 -2
- package/tools/tools.openai.json +42 -2
- package/tools/tools.vercel.json +42 -2
package/README.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# @superdoc/sdk
|
|
2
2
|
|
|
3
|
-
Node.js SDK for reading and editing DOCX files with SuperDoc
|
|
3
|
+
Node.js SDK for reading and editing DOCX files with SuperDoc, without a
|
|
4
|
+
browser. Query, edit, comment, and review tracked changes from your server,
|
|
5
|
+
or give an LLM SuperDoc tools with the agent toolkit. Edits write back to
|
|
6
|
+
OOXML, so the output opens in Word. For an interactive editor, use
|
|
7
|
+
[`superdoc`](https://www.npmjs.com/package/superdoc).
|
|
8
|
+
|
|
9
|
+
[Documentation](https://docs.superdoc.dev/agents/automation/node-sdk) · [GitHub](https://github.com/superdoc/docx-editor)
|
|
4
10
|
|
|
5
11
|
## Install
|
|
6
12
|
|
|
@@ -8,23 +14,37 @@ Node.js SDK for reading and editing DOCX files with SuperDoc.
|
|
|
8
14
|
npm install @superdoc/sdk
|
|
9
15
|
```
|
|
10
16
|
|
|
17
|
+
The SDK embeds the SuperDoc runtime for your platform. You do not need a
|
|
18
|
+
separate CLI installation.
|
|
19
|
+
|
|
11
20
|
## Quick start
|
|
12
21
|
|
|
13
22
|
```js
|
|
14
23
|
import { SuperDocClient } from '@superdoc/sdk';
|
|
15
24
|
|
|
16
|
-
const client = new SuperDocClient();
|
|
17
|
-
await client.connect();
|
|
25
|
+
const client = new SuperDocClient({ user: { name: 'Contract bot', email: 'bot@example.com' } });
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
try {
|
|
28
|
+
await client.connect();
|
|
29
|
+
const doc = await client.open({ doc: './contract.docx' });
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
try {
|
|
32
|
+
const receipt = await doc.trackChanges.decide({ decision: 'accept', target: { kind: 'all' } });
|
|
33
|
+
if (!receipt.success) throw new Error('Accepting tracked changes failed.');
|
|
23
34
|
|
|
24
|
-
await
|
|
25
|
-
|
|
35
|
+
await doc.save({ out: './contract.accepted.docx', force: true });
|
|
36
|
+
} finally {
|
|
37
|
+
await doc.close({ discard: true });
|
|
38
|
+
}
|
|
39
|
+
} finally {
|
|
40
|
+
await client.dispose();
|
|
41
|
+
}
|
|
26
42
|
```
|
|
27
43
|
|
|
44
|
+
Save to a new path so the source stays unchanged. `force: true` lets the save
|
|
45
|
+
replace an earlier output. `close({ discard: true })` then closes the session
|
|
46
|
+
without writing over the source.
|
|
47
|
+
|
|
28
48
|
## Client lifetime
|
|
29
49
|
|
|
30
50
|
One client owns one SuperDoc host process. `connect()` starts it once; every
|
|
@@ -37,6 +57,131 @@ room sync on every call. To cut round trips further, batch related edits with
|
|
|
37
57
|
`document.mutations.apply()` or arbitrary operations with
|
|
38
58
|
`document.plan.execute()`.
|
|
39
59
|
|
|
60
|
+
### Batch timeouts and recovery
|
|
61
|
+
|
|
62
|
+
Use `mutations.apply({ atomic: true, steps, changeMode: 'tracked' })` for related
|
|
63
|
+
structured rewrites instead of a separate search and edit request for every
|
|
64
|
+
replacement. Resolve targets from the current document and inspect the batch
|
|
65
|
+
receipt. Choose batch sizes from your workload's measurements; there is no
|
|
66
|
+
recommended fixed cap.
|
|
67
|
+
|
|
68
|
+
`requestTimeoutMs` sets the host's default request budget. A per-call
|
|
69
|
+
`{ timeoutMs }` overrides it on hosts that support request timeout metadata.
|
|
70
|
+
The SDK watchdog waits longer than that budget so the host can return a
|
|
71
|
+
structured `TIMEOUT`. A timeout is a response deadline, not mutation rollback.
|
|
72
|
+
|
|
73
|
+
When the host's response deadline expires, `TIMEOUT` details describe execution and retry safety:
|
|
74
|
+
|
|
75
|
+
- `executionState: 'not-started'`, `retrySafe: true`: the request expired in
|
|
76
|
+
the host queue and will not run. Retrying cannot duplicate that request's edits.
|
|
77
|
+
- `executionState: 'unknown'`, `retrySafe: false`: invocation started and may
|
|
78
|
+
still finish. Do not replay the batch. Read the same document's text and tracked
|
|
79
|
+
changes with a sufficient budget; the host serializes those reads after the
|
|
80
|
+
timed-out invocation settles. Compare the actual result with the intended
|
|
81
|
+
changes, then build a fresh plan only for work that remains.
|
|
82
|
+
- `executionState: 'unknown'`, `retrySafe: true`: a recognized change-list long
|
|
83
|
+
poll already started a read. Retrying this read-only poll cannot duplicate edits,
|
|
84
|
+
even when its final re-query expires in the queue.
|
|
85
|
+
|
|
86
|
+
Treat absent recovery details as an unknown outcome, including errors from
|
|
87
|
+
older hosts or an operation's own timeout handling.
|
|
88
|
+
`HOST_TIMEOUT` is the separate SDK watchdog error: the host did not respond and
|
|
89
|
+
the SDK disconnects it. It does not prove that edits were rolled back. Preserve
|
|
90
|
+
the session state and inspect the recoverable working document before creating
|
|
91
|
+
another session or retrying edits. A permanently stalled invocation can also
|
|
92
|
+
block later reads; another timeout is not evidence that nothing applied.
|
|
93
|
+
|
|
94
|
+
## Common tasks
|
|
95
|
+
|
|
96
|
+
### Read the document
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
const markdown = await doc.getMarkdown();
|
|
100
|
+
const text = await doc.getText({});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Find and replace text as a tracked change
|
|
104
|
+
|
|
105
|
+
`query.match` returns a complete target. Pass it to the mutation instead of
|
|
106
|
+
building one yourself:
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
const match = await doc.query.match({
|
|
110
|
+
select: { type: 'text', pattern: 'termination' },
|
|
111
|
+
require: 'first',
|
|
112
|
+
});
|
|
113
|
+
const item = match.items[0];
|
|
114
|
+
if (!item || item.matchKind !== 'text') throw new Error('Text not found.');
|
|
115
|
+
|
|
116
|
+
const operation = await doc.replace({
|
|
117
|
+
target: item.target,
|
|
118
|
+
text: 'cancellation',
|
|
119
|
+
changeMode: 'tracked',
|
|
120
|
+
});
|
|
121
|
+
const receipt = 'receipt' in operation ? operation.receipt : operation;
|
|
122
|
+
if (!receipt.success) throw new Error('Creating the tracked change failed.');
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Give an LLM SuperDoc tools
|
|
126
|
+
|
|
127
|
+
`createAgentToolkit` returns tool definitions, a matching system prompt, and a
|
|
128
|
+
`dispatch` function, so the three cannot disagree:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
import { createAgentToolkit } from '@superdoc/sdk';
|
|
132
|
+
|
|
133
|
+
const { tools, systemPrompt, dispatch } = await createAgentToolkit({
|
|
134
|
+
provider: 'openai', // 'openai' | 'anthropic' | 'vercel' | 'generic'
|
|
135
|
+
preset: 'core',
|
|
136
|
+
enforceChangeMode: 'tracked',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// For each tool call in an OpenAI Chat Completions reply:
|
|
140
|
+
const result = await dispatch(doc, call.function.name, JSON.parse(call.function.arguments));
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
OpenAI sends tool arguments as a JSON string, so parse them before calling
|
|
144
|
+
`dispatch`. Anthropic and Vercel pass an object already.
|
|
145
|
+
|
|
146
|
+
`enforceChangeMode: 'tracked'` hides actions that cannot record a tracked
|
|
147
|
+
change and records every other edit as tracked. See
|
|
148
|
+
[build an agent](https://docs.superdoc.dev/agents/build/build-an-agent) for the
|
|
149
|
+
full loop.
|
|
150
|
+
|
|
151
|
+
## Common mistakes
|
|
152
|
+
|
|
153
|
+
| Wrong | Right |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| Creating a client per operation | Create one per service and keep document handles open |
|
|
156
|
+
| Passing OpenAI's JSON-string arguments to `dispatch` | `JSON.parse` them first. `dispatch` takes an object |
|
|
157
|
+
| Omitting `preset` in `createAgentToolkit` | Pass `preset: 'core'`. Omitting it selects the older `legacy` tools |
|
|
158
|
+
| Omitting `user` | Name the workflow so tracked changes show who proposed them |
|
|
159
|
+
| Reusing a `query.match` target after an edit | Query again. Targets are bound to the revision that produced them |
|
|
160
|
+
| Building targets from OOXML IDs such as `w14:paraId` | Use the address or target the API returns |
|
|
161
|
+
| Saving over the source while testing | Save to a new path with `out` |
|
|
162
|
+
| Retrying a failed receipt automatically | Inspect `executedOperations` first. Part of the edit may have committed |
|
|
163
|
+
| Importing `superdoc` in Node.js | Use this package. `superdoc` is the browser editor |
|
|
164
|
+
|
|
165
|
+
## Related packages
|
|
166
|
+
|
|
167
|
+
- [`superdoc`](https://www.npmjs.com/package/superdoc): browser DOCX editor for reviewing the SDK's output.
|
|
168
|
+
- [`@superdoc/mcp`](https://www.npmjs.com/package/@superdoc/mcp): the same tools for an existing MCP client, without writing an agent loop.
|
|
169
|
+
- [`@superdoc/cli`](https://www.npmjs.com/package/@superdoc/cli): the same operations from a terminal or script.
|
|
170
|
+
- [`superdoc-sdk`](https://pypi.org/project/superdoc-sdk/) on PyPI and [`superdoc-sdk`](https://crates.io/crates/superdoc-sdk) on crates.io: Python and Rust SDKs.
|
|
171
|
+
|
|
172
|
+
## Documentation
|
|
173
|
+
|
|
174
|
+
- [Node.js SDK guide](https://docs.superdoc.dev/agents/automation/node-sdk)
|
|
175
|
+
- [Build an agent](https://docs.superdoc.dev/agents/build/build-an-agent)
|
|
176
|
+
- [Document API mental model](https://docs.superdoc.dev/document-api/mental-model)
|
|
177
|
+
- [Document comparison](https://docs.superdoc.dev/document-api/document-comparison)
|
|
178
|
+
- [Agent safety](https://docs.superdoc.dev/agents/operate/safety)
|
|
179
|
+
- [`llms.txt`](https://docs.superdoc.dev/llms.txt): machine-readable index of the documentation for coding agents.
|
|
180
|
+
|
|
181
|
+
## Advanced
|
|
182
|
+
|
|
183
|
+
### State directory
|
|
184
|
+
|
|
40
185
|
For read-only hosts and containers, configure a writable state directory:
|
|
41
186
|
|
|
42
187
|
```js
|
|
@@ -73,6 +218,8 @@ the SDK parent can leave temporary state behind; the operating system or host
|
|
|
73
218
|
operator must reclaim it. A new client gets a separate private root and does
|
|
74
219
|
not sweep another client's state.
|
|
75
220
|
|
|
221
|
+
### Authenticated collaboration rooms
|
|
222
|
+
|
|
76
223
|
For token-authenticated Hocuspocus or y-websocket rooms, pass the credential as
|
|
77
224
|
an option on that document's open request:
|
|
78
225
|
|
|
@@ -94,9 +241,41 @@ The credential applies only to that open request. It is not part of the
|
|
|
94
241
|
collaboration profile or command-line arguments. Per-open token authentication
|
|
95
242
|
is not supported for Liveblocks.
|
|
96
243
|
|
|
97
|
-
|
|
244
|
+
### Attributed change records
|
|
98
245
|
|
|
99
|
-
|
|
246
|
+
Give each session a stable actor id with `userId`. Pass an application-signed
|
|
247
|
+
claim, if you have one, as the `identityClaim` open option; like the
|
|
248
|
+
collaboration token, it is sent only with that open request and is never
|
|
249
|
+
written to arguments or state. SuperDoc records the claim but does not verify
|
|
250
|
+
it.
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
const document = await client.open(
|
|
254
|
+
{ doc: './contract.docx', userId: 'user-42', collaboration },
|
|
255
|
+
{ collaborationAuth: { type: 'token', token: userToken }, identityClaim: signedClaim },
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Every local and remote edit produces a change record. Read them with
|
|
260
|
+
`document.changes.list({ after, waitMs })`; `waitMs` waits on the host without
|
|
261
|
+
blocking other requests. To be told about new records, subscribe to host
|
|
262
|
+
notifications:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
const notifications = client.subscribe();
|
|
266
|
+
for await (const notification of notifications) {
|
|
267
|
+
if (notification.method === 'event.change') handle(notification.params.records);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Each subscription holds up to `capacity` notifications (default 64). If you
|
|
272
|
+
fall behind, the next read rejects with `NOTIFICATIONS_LAGGED`; resynchronize
|
|
273
|
+
with `changes.list({ after })` from the last cursor you processed, then keep
|
|
274
|
+
iterating. A host disconnect rejects one read with `HOST_DISCONNECTED`.
|
|
275
|
+
`notifications.close()` unsubscribes and `client.dispose()` ends every
|
|
276
|
+
subscription.
|
|
277
|
+
|
|
278
|
+
### Discover footer paragraphs
|
|
100
279
|
|
|
101
280
|
`blocks.list({ in: story })` lists top-level blocks. To discover paragraphs
|
|
102
281
|
inside footer tables, including tables wrapped in block content controls, use
|
|
@@ -132,7 +311,47 @@ if (matches.total === 1) {
|
|
|
132
311
|
}
|
|
133
312
|
```
|
|
134
313
|
|
|
135
|
-
|
|
314
|
+
### Inspecting textbox sections
|
|
315
|
+
|
|
316
|
+
`superdoc_inspect` includes discovered text-bearing textbox stories in `textboxes` when
|
|
317
|
+
block inspection is enabled. Each entry contains its explicit `story` locator,
|
|
318
|
+
its story-local `total`, and normalized `blocks` with text, identities, and list
|
|
319
|
+
markers. The top-level `blocks` array and its ordinals continue to refer to the
|
|
320
|
+
body; textbox block ordinals belong to their containing story.
|
|
321
|
+
|
|
322
|
+
The block offset, limit, text cap, and block filters also apply within each
|
|
323
|
+
textbox. Count-only inspections and inspections excluding the blocks domain do
|
|
324
|
+
not collect textbox text. Check `diagnostics` for incomplete story discovery or
|
|
325
|
+
reads. To edit a discovered heading, use `query.match` with its story locator
|
|
326
|
+
and pass the returned reference to `replace`; a textbox ordinal is not a body
|
|
327
|
+
action selector.
|
|
328
|
+
|
|
329
|
+
### Rejectable comparison styles
|
|
330
|
+
|
|
331
|
+
For standalone V2 comparisons, use the tracked style policy when reviewers need to
|
|
332
|
+
reject proposed styling as well as text:
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
const diff = await older.diff.compare({ targetSnapshot, styleChangeMode: 'tracked' });
|
|
336
|
+
const result = await older.diff.apply({ diff, changeMode: 'tracked', styleChangeMode: 'tracked' });
|
|
337
|
+
await older.save({ out: 'comparison.docx' });
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The saved file contains separate proposed style definitions and tracked references
|
|
341
|
+
that Word can accept or reject. Rejection restores the original text and effective
|
|
342
|
+
styling. Unused definitions may remain after a decision, and proposed styles retain
|
|
343
|
+
their distinct names. Inspect `result.styleChanges` and `result.diagnostics`.
|
|
344
|
+
|
|
345
|
+
This policy supports non-default paragraph and character styles in the main body,
|
|
346
|
+
including inherited styles and outline-based TOCs. TOCs that select an affected style
|
|
347
|
+
by identity (`\o`) or name (`\t`) are refused. Defaults, table styles, affected styles in other stories,
|
|
348
|
+
combined table topology changes, pre-existing revisions, and definitions that activate already-used unresolved style
|
|
349
|
+
references are refused. Numbering
|
|
350
|
+
and settings retain their restrictions. Versioned suggest-only review sessions have
|
|
351
|
+
a separate policy and refuse this option. Read eligibility before applying and
|
|
352
|
+
handle apply-time refusals without saving a result.
|
|
353
|
+
|
|
354
|
+
### Tracked comparison with direct style definitions
|
|
136
355
|
|
|
137
356
|
For standalone V2 comparisons, `styleChangeMode: 'direct'` lets you explicitly
|
|
138
357
|
carry the target style definitions while applying body edits as tracked changes:
|
|
@@ -147,12 +366,12 @@ await older.save({ out: 'comparison.docx' });
|
|
|
147
366
|
Set the policy on both calls: compare reports eligibility for that policy;
|
|
148
367
|
apply requires its own explicit consent. Style definitions are applied directly
|
|
149
368
|
and remain if the body revisions are rejected in Word. A style-only comparison
|
|
150
|
-
creates no body revisions. Without
|
|
369
|
+
creates no body revisions. Without an explicit style policy, changed definitions still
|
|
151
370
|
block tracked apply. Unsupported numbering, settings, and other package changes
|
|
152
371
|
retain their existing restrictions. Suggest-only versioned review sessions
|
|
153
372
|
refuse this direct-style option.
|
|
154
373
|
|
|
155
|
-
|
|
374
|
+
### Tracked comparison with exact-file review
|
|
156
375
|
|
|
157
376
|
The SDK's `diff.capture` and `diff.compare` describe a snapshot comparison. A
|
|
158
377
|
blocked tracked `diff.apply` cannot turn that snapshot into a complete review
|
|
@@ -216,7 +435,7 @@ endpoints; other supported changes become one whole-document proposal. A stale
|
|
|
216
435
|
base or invalid DOCX returns a refusal without a partial apply. The host owns
|
|
217
436
|
authorization, durable storage, reviewer presentation, and decisions.
|
|
218
437
|
|
|
219
|
-
|
|
438
|
+
### Agent execution evidence
|
|
220
439
|
|
|
221
440
|
The core preset's `agent_apply`, `agent_verify`, and supported named actions
|
|
222
441
|
collect the facts their targets and checks need. CLI-backed sessions
|
|
@@ -262,19 +481,7 @@ automatically replay the write.
|
|
|
262
481
|
|
|
263
482
|
## License
|
|
264
483
|
|
|
265
|
-
AGPL-3.0. Commercial licenses
|
|
266
|
-
|
|
267
|
-
## Inspecting textbox sections
|
|
484
|
+
AGPL-3.0. [Commercial licenses](https://www.superdoc.dev/#pricing) are
|
|
485
|
+
available for proprietary deployments.
|
|
268
486
|
|
|
269
|
-
|
|
270
|
-
block inspection is enabled. Each entry contains its explicit `story` locator,
|
|
271
|
-
its story-local `total`, and normalized `blocks` with text, identities, and list
|
|
272
|
-
markers. The top-level `blocks` array and its ordinals continue to refer to the
|
|
273
|
-
body; textbox block ordinals belong to their containing story.
|
|
274
|
-
|
|
275
|
-
The block offset, limit, text cap, and block filters also apply within each
|
|
276
|
-
textbox. Count-only inspections and inspections excluding the blocks domain do
|
|
277
|
-
not collect textbox text. Check `diagnostics` for incomplete story discovery or
|
|
278
|
-
reads. To edit a discovered heading, use `query.match` with its story locator
|
|
279
|
-
and pass the returned reference to `replace`; a textbox ordinal is not a body
|
|
280
|
-
action selector.
|
|
487
|
+
The retained legacy comparison adapter refuses tracked style policy with `CAPABILITY_UNSUPPORTED`; use the current typed engine through the standalone SDK.
|
package/dist/agent/actions.cjs
CHANGED
|
@@ -5398,6 +5398,9 @@ function matchRunStyle(run) {
|
|
|
5398
5398
|
...(color ? { color } : {}),
|
|
5399
5399
|
};
|
|
5400
5400
|
}
|
|
5401
|
+
function sameMatchRunStyle(a, b) {
|
|
5402
|
+
return ['bold', 'italic', 'underline', 'fontSize', 'fontFamily', 'color'].every((key) => a[key] === b[key]);
|
|
5403
|
+
}
|
|
5401
5404
|
/**
|
|
5402
5405
|
* A sampled style as a `format.apply` `inline` payload.
|
|
5403
5406
|
*
|
|
@@ -5466,36 +5469,71 @@ async function listItemRunPattern(doc, text, blockId) {
|
|
|
5466
5469
|
if (asString(rec?.blockId) !== blockId)
|
|
5467
5470
|
continue;
|
|
5468
5471
|
const runTextOf = (r) => asString(asRecord(r)?.text, '') ?? '';
|
|
5469
|
-
const
|
|
5470
|
-
if (
|
|
5472
|
+
const sourceRuns = (Array.isArray(rec?.runs) ? rec.runs : []).filter((run) => runTextOf(run));
|
|
5473
|
+
if (sourceRuns.length === 0)
|
|
5471
5474
|
continue;
|
|
5472
|
-
const typedLead = typedPrefixLabelOf(runs, runTextOf);
|
|
5473
|
-
if (typedLead)
|
|
5474
|
-
return typedLead;
|
|
5475
5475
|
const blockText = asString(rec?.text, '') ?? '';
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
const
|
|
5480
|
-
const
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5476
|
+
const rawPattern = runPatternFromRuns(sourceRuns, blockText, runTextOf);
|
|
5477
|
+
if (rawPattern.lead)
|
|
5478
|
+
return rawPattern;
|
|
5479
|
+
const runs = [];
|
|
5480
|
+
for (const run of sourceRuns) {
|
|
5481
|
+
const current = asRecord(run);
|
|
5482
|
+
const text = runTextOf(current);
|
|
5483
|
+
if (!current || !text)
|
|
5484
|
+
continue;
|
|
5485
|
+
const previous = runs.at(-1);
|
|
5486
|
+
const previousRange = asRecord(previous?.range);
|
|
5487
|
+
const currentRange = asRecord(current.range);
|
|
5488
|
+
const contiguous = previousRange || currentRange
|
|
5489
|
+
? typeof previousRange?.start === 'number' &&
|
|
5490
|
+
typeof previousRange.end === 'number' &&
|
|
5491
|
+
typeof currentRange?.start === 'number' &&
|
|
5492
|
+
typeof currentRange?.end === 'number' &&
|
|
5493
|
+
previousRange.end === currentRange.start
|
|
5494
|
+
: true;
|
|
5495
|
+
const previousStyle = matchRunStyle(previous);
|
|
5496
|
+
const currentStyle = matchRunStyle(current);
|
|
5497
|
+
const equalStyle = sameMatchRunStyle(previousStyle, currentStyle);
|
|
5498
|
+
if (previous && contiguous && equalStyle) {
|
|
5499
|
+
previous.text = runTextOf(previous) + text;
|
|
5500
|
+
if (previousRange && currentRange)
|
|
5501
|
+
previous.range = { ...previousRange, end: currentRange.end };
|
|
5502
|
+
}
|
|
5503
|
+
else {
|
|
5504
|
+
runs.push({ ...current });
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
const normalizedPattern = runPatternFromRuns(runs, blockText, runTextOf);
|
|
5508
|
+
return normalizedPattern.lead ? normalizedPattern : rawPattern;
|
|
5495
5509
|
}
|
|
5496
5510
|
}
|
|
5497
5511
|
return null;
|
|
5498
5512
|
}
|
|
5513
|
+
function runPatternFromRuns(runs, blockText, runTextOf) {
|
|
5514
|
+
const typedLead = typedPrefixLabelOf(runs, runTextOf);
|
|
5515
|
+
if (typedLead)
|
|
5516
|
+
return typedLead;
|
|
5517
|
+
// A quoted defined term keeps its opening quote in a run of its own —
|
|
5518
|
+
// `"` + **Deliverables** + `" means …` — so the label is the SECOND run
|
|
5519
|
+
// and the quotes stay unemphasised.
|
|
5520
|
+
const opener = runTextOf(runs[0]).trim();
|
|
5521
|
+
const quote = LEAD_IN_QUOTES.find((q) => opener === q);
|
|
5522
|
+
const labelIndex = quote && runs.length > 2 ? 1 : 0;
|
|
5523
|
+
const label = runs[labelIndex];
|
|
5524
|
+
const labelText = runTextOf(label);
|
|
5525
|
+
const leadStyle = matchRunStyle(label);
|
|
5526
|
+
if (runs.length === 1)
|
|
5527
|
+
return runPatternOf(leadStyle);
|
|
5528
|
+
// The body is the run the label INTRODUCES, not the longest run in the
|
|
5529
|
+
// block: a later emphasised phrase is part of the prose, and sampling it
|
|
5530
|
+
// would stamp its emphasis across the whole new block.
|
|
5531
|
+
const bodyStyle = matchRunStyle(runs[labelIndex + 1]);
|
|
5532
|
+
const differs = !sameMatchRunStyle(leadStyle, bodyStyle);
|
|
5533
|
+
const shape = leadInShapeOf(blockText, labelText, labelIndex > 0 ? quote : undefined);
|
|
5534
|
+
const isLabel = differs && shape != null && labelText.length > 0 && labelText.length <= LEAD_IN_MAX_CHARS;
|
|
5535
|
+
return runPatternOf(bodyStyle, isLabel && shape ? { style: leadStyle, shape } : undefined);
|
|
5536
|
+
}
|
|
5499
5537
|
function typedPrefixLabelOf(runs, runTextOf) {
|
|
5500
5538
|
const typed = typedMarkerPrefixOf(runs.map(runTextOf).join(''));
|
|
5501
5539
|
if (!typed)
|
package/dist/agent/actions.js
CHANGED
|
@@ -5395,6 +5395,9 @@ function matchRunStyle(run) {
|
|
|
5395
5395
|
...(color ? { color } : {}),
|
|
5396
5396
|
};
|
|
5397
5397
|
}
|
|
5398
|
+
function sameMatchRunStyle(a, b) {
|
|
5399
|
+
return ['bold', 'italic', 'underline', 'fontSize', 'fontFamily', 'color'].every((key) => a[key] === b[key]);
|
|
5400
|
+
}
|
|
5398
5401
|
/**
|
|
5399
5402
|
* A sampled style as a `format.apply` `inline` payload.
|
|
5400
5403
|
*
|
|
@@ -5463,36 +5466,71 @@ async function listItemRunPattern(doc, text, blockId) {
|
|
|
5463
5466
|
if (asString(rec?.blockId) !== blockId)
|
|
5464
5467
|
continue;
|
|
5465
5468
|
const runTextOf = (r) => asString(asRecord(r)?.text, '') ?? '';
|
|
5466
|
-
const
|
|
5467
|
-
if (
|
|
5469
|
+
const sourceRuns = (Array.isArray(rec?.runs) ? rec.runs : []).filter((run) => runTextOf(run));
|
|
5470
|
+
if (sourceRuns.length === 0)
|
|
5468
5471
|
continue;
|
|
5469
|
-
const typedLead = typedPrefixLabelOf(runs, runTextOf);
|
|
5470
|
-
if (typedLead)
|
|
5471
|
-
return typedLead;
|
|
5472
5472
|
const blockText = asString(rec?.text, '') ?? '';
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
const
|
|
5477
|
-
const
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5473
|
+
const rawPattern = runPatternFromRuns(sourceRuns, blockText, runTextOf);
|
|
5474
|
+
if (rawPattern.lead)
|
|
5475
|
+
return rawPattern;
|
|
5476
|
+
const runs = [];
|
|
5477
|
+
for (const run of sourceRuns) {
|
|
5478
|
+
const current = asRecord(run);
|
|
5479
|
+
const text = runTextOf(current);
|
|
5480
|
+
if (!current || !text)
|
|
5481
|
+
continue;
|
|
5482
|
+
const previous = runs.at(-1);
|
|
5483
|
+
const previousRange = asRecord(previous?.range);
|
|
5484
|
+
const currentRange = asRecord(current.range);
|
|
5485
|
+
const contiguous = previousRange || currentRange
|
|
5486
|
+
? typeof previousRange?.start === 'number' &&
|
|
5487
|
+
typeof previousRange.end === 'number' &&
|
|
5488
|
+
typeof currentRange?.start === 'number' &&
|
|
5489
|
+
typeof currentRange?.end === 'number' &&
|
|
5490
|
+
previousRange.end === currentRange.start
|
|
5491
|
+
: true;
|
|
5492
|
+
const previousStyle = matchRunStyle(previous);
|
|
5493
|
+
const currentStyle = matchRunStyle(current);
|
|
5494
|
+
const equalStyle = sameMatchRunStyle(previousStyle, currentStyle);
|
|
5495
|
+
if (previous && contiguous && equalStyle) {
|
|
5496
|
+
previous.text = runTextOf(previous) + text;
|
|
5497
|
+
if (previousRange && currentRange)
|
|
5498
|
+
previous.range = { ...previousRange, end: currentRange.end };
|
|
5499
|
+
}
|
|
5500
|
+
else {
|
|
5501
|
+
runs.push({ ...current });
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
const normalizedPattern = runPatternFromRuns(runs, blockText, runTextOf);
|
|
5505
|
+
return normalizedPattern.lead ? normalizedPattern : rawPattern;
|
|
5492
5506
|
}
|
|
5493
5507
|
}
|
|
5494
5508
|
return null;
|
|
5495
5509
|
}
|
|
5510
|
+
function runPatternFromRuns(runs, blockText, runTextOf) {
|
|
5511
|
+
const typedLead = typedPrefixLabelOf(runs, runTextOf);
|
|
5512
|
+
if (typedLead)
|
|
5513
|
+
return typedLead;
|
|
5514
|
+
// A quoted defined term keeps its opening quote in a run of its own —
|
|
5515
|
+
// `"` + **Deliverables** + `" means …` — so the label is the SECOND run
|
|
5516
|
+
// and the quotes stay unemphasised.
|
|
5517
|
+
const opener = runTextOf(runs[0]).trim();
|
|
5518
|
+
const quote = LEAD_IN_QUOTES.find((q) => opener === q);
|
|
5519
|
+
const labelIndex = quote && runs.length > 2 ? 1 : 0;
|
|
5520
|
+
const label = runs[labelIndex];
|
|
5521
|
+
const labelText = runTextOf(label);
|
|
5522
|
+
const leadStyle = matchRunStyle(label);
|
|
5523
|
+
if (runs.length === 1)
|
|
5524
|
+
return runPatternOf(leadStyle);
|
|
5525
|
+
// The body is the run the label INTRODUCES, not the longest run in the
|
|
5526
|
+
// block: a later emphasised phrase is part of the prose, and sampling it
|
|
5527
|
+
// would stamp its emphasis across the whole new block.
|
|
5528
|
+
const bodyStyle = matchRunStyle(runs[labelIndex + 1]);
|
|
5529
|
+
const differs = !sameMatchRunStyle(leadStyle, bodyStyle);
|
|
5530
|
+
const shape = leadInShapeOf(blockText, labelText, labelIndex > 0 ? quote : undefined);
|
|
5531
|
+
const isLabel = differs && shape != null && labelText.length > 0 && labelText.length <= LEAD_IN_MAX_CHARS;
|
|
5532
|
+
return runPatternOf(bodyStyle, isLabel && shape ? { style: leadStyle, shape } : undefined);
|
|
5533
|
+
}
|
|
5496
5534
|
function typedPrefixLabelOf(runs, runTextOf) {
|
|
5497
5535
|
const typed = typedMarkerPrefixOf(runs.map(runTextOf).join(''));
|
|
5498
5536
|
if (!typed)
|
package/dist/agent/catalog.d.ts
CHANGED
|
@@ -388,10 +388,6 @@ export interface ResolveActionPolicyOptions {
|
|
|
388
388
|
ignoreUnknownActions?: boolean;
|
|
389
389
|
}
|
|
390
390
|
export declare function resolveActionPolicy(options?: ListAgentToolsOptions, resolveOptions?: ResolveActionPolicyOptions): ResolvedActionPolicy;
|
|
391
|
-
/** @deprecated Use {@link resolveActionPolicy}; kept for callers that only need the union. */
|
|
392
|
-
export declare function validateExclusions(options?: ListAgentToolsOptions): {
|
|
393
|
-
excludedActions: Set<string>;
|
|
394
|
-
};
|
|
395
391
|
/**
|
|
396
392
|
* Tools advertised to the LLM. Only PUBLIC_AGENT_TOOL_NAMES (currently
|
|
397
393
|
* superdoc_inspect + superdoc_perform_action) are returned; the remaining
|
package/dist/agent/catalog.js
CHANGED
|
@@ -614,10 +614,6 @@ export function resolveActionPolicy(options, resolveOptions) {
|
|
|
614
614
|
allowDirectActions,
|
|
615
615
|
};
|
|
616
616
|
}
|
|
617
|
-
/** @deprecated Use {@link resolveActionPolicy}; kept for callers that only need the union. */
|
|
618
|
-
export function validateExclusions(options) {
|
|
619
|
-
return { excludedActions: resolveActionPolicy(options).excludedActions };
|
|
620
|
-
}
|
|
621
617
|
/**
|
|
622
618
|
* Tools advertised to the LLM. Only PUBLIC_AGENT_TOOL_NAMES (currently
|
|
623
619
|
* superdoc_inspect + superdoc_perform_action) are returned; the remaining
|