@superdoc/sdk 2.6.0 → 2.8.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/dist/agent/actions.cjs +1869 -160
- package/dist/agent/actions.d.ts +77 -10
- package/dist/agent/actions.js +1870 -161
- package/dist/agent/catalog.cjs +102 -9
- package/dist/agent/catalog.d.ts +243 -0
- package/dist/agent/catalog.js +99 -9
- package/dist/agent/doc-snapshot.cjs +200 -2
- package/dist/agent/doc-snapshot.d.ts +91 -0
- package/dist/agent/doc-snapshot.js +199 -2
- package/dist/agent/runtime.cjs +9 -1
- package/dist/agent/runtime.d.ts +8 -0
- package/dist/agent/runtime.js +9 -1
- package/dist/generated/client.cjs +754 -770
- package/dist/generated/client.d.ts +10 -9
- package/dist/generated/client.js +754 -770
- package/dist/generated/contract.cjs +16178 -272
- package/dist/generated/contract.d.ts +38 -0
- package/dist/generated/contract.js +17419 -1510
- package/dist/index.cjs +6 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -5
- package/dist/introspection.cjs +59 -0
- package/dist/introspection.d.ts +3 -0
- package/dist/introspection.js +53 -0
- package/dist/runtime/document-rpc.cjs +179 -40
- package/dist/runtime/document-rpc.d.ts +13 -4
- package/dist/runtime/document-rpc.js +175 -40
- package/dist/runtime/embedded-cli.cjs +5 -68
- package/dist/runtime/embedded-cli.js +5 -67
- package/dist/runtime/embedded-document-host.cjs +28 -0
- package/dist/runtime/embedded-document-host.d.ts +1 -0
- package/dist/runtime/embedded-document-host.js +23 -0
- package/dist/runtime/embedded-platform.cjs +108 -0
- package/dist/runtime/embedded-platform.d.ts +5 -0
- package/dist/runtime/embedded-platform.js +99 -0
- package/dist/runtime/host.cjs +237 -24
- package/dist/runtime/host.d.ts +17 -0
- package/dist/runtime/host.js +237 -25
- package/dist/runtime/process.cjs +30 -9
- package/dist/runtime/process.d.ts +9 -0
- package/dist/runtime/process.js +29 -9
- package/dist/runtime/sdk-version.generated.cjs +8 -0
- package/dist/runtime/sdk-version.generated.d.ts +1 -0
- package/dist/runtime/sdk-version.generated.js +4 -0
- package/dist/runtime/transport-common.cjs +1 -0
- package/dist/runtime/transport-common.d.ts +28 -10
- package/dist/runtime/transport-common.js +1 -1
- package/package.json +7 -7
- package/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- package/tools/__pycache__/intent_dispatch_generated.cpython-311.pyc +0 -0
- package/tools/tools-policy.json +1 -1
package/dist/runtime/host.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
3
|
import { buildOperationArgv, resolveInvocation, } from './transport-common.js';
|
|
4
4
|
import { SuperDocCliError } from './errors.js';
|
|
5
|
-
import { DOCUMENT_RPC_FEATURES, buildDocumentInvokeParams, buildDocumentOpenParams, canOpenWithDocumentRpc, documentRpcEnabled, mapDocumentLifecycleResult, mapDocumentOpenResult, supportsDocumentRpc, } from './document-rpc.js';
|
|
5
|
+
import { DOCUMENT_RPC_FEATURES, DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE, DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE, DOCUMENT_RPC_SOURCE_SAVE_FEATURE, buildDocumentInvokeParams, buildDocumentOpenParams, canOpenWithDocumentRpc, documentRpcRequestSupportsResponseTimeout, documentRpcEnabled, mapDocumentLifecycleResult, mapDocumentOpenResult, supportsDocumentRpc, } from './document-rpc.js';
|
|
6
6
|
const HOST_PROTOCOL_VERSION = '1.0';
|
|
7
7
|
const CLI_HOST_REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
|
|
8
8
|
const DOCUMENT_HOST_REQUIRED_FEATURES = [...DOCUMENT_RPC_FEATURES, 'host.shutdown'];
|
|
@@ -20,6 +20,21 @@ const HOST_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
|
20
20
|
// abort. The buffer absorbs JSON-RPC serialization, stdio drain, and event-
|
|
21
21
|
// loop latency.
|
|
22
22
|
const WATCHDOG_HEADROOM_MS = 5_000;
|
|
23
|
+
export function mapCliInvocationResult(operation, value) {
|
|
24
|
+
const envelopeKey = operation.responseEnvelopeKey;
|
|
25
|
+
if (envelopeKey === null || envelopeKey === undefined)
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof envelopeKey !== 'string' || envelopeKey.length === 0) {
|
|
28
|
+
throw new SuperDocCliError('Generated operation has invalid response envelope metadata.', {
|
|
29
|
+
code: 'HOST_PROTOCOL_ERROR',
|
|
30
|
+
details: { operationId: operation.operationId, responseEnvelopeKey: envelopeKey },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
34
|
+
return value;
|
|
35
|
+
const extracted = value[envelopeKey];
|
|
36
|
+
return extracted === undefined ? value : extracted;
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* Builds the argv passed to `spawn` for `superdoc host --stdio`. Propagates
|
|
25
40
|
* `requestTimeoutMs` to the host via `--request-timeout-ms`, since the SDK
|
|
@@ -80,9 +95,15 @@ export class HostTransport {
|
|
|
80
95
|
pending = new Map();
|
|
81
96
|
nextRequestId = 1;
|
|
82
97
|
connecting = null;
|
|
98
|
+
disposePromise = null;
|
|
99
|
+
terminalDisposalError = null;
|
|
83
100
|
stopping = false;
|
|
101
|
+
activeCalls = new Set();
|
|
84
102
|
hostFeatures = new Set();
|
|
85
103
|
documentRpcSessions = new Set();
|
|
104
|
+
dirtyDocumentRpcSessions = new Set();
|
|
105
|
+
documentRpcMutationStates = new Map();
|
|
106
|
+
indeterminateDiscardCloses = new Set();
|
|
86
107
|
constructor(options) {
|
|
87
108
|
this.hostBin = options.hostBin;
|
|
88
109
|
this.processMode = options.processMode ?? 'cli';
|
|
@@ -98,22 +119,48 @@ export class HostTransport {
|
|
|
98
119
|
details: { defaultChangeMode: options.defaultChangeMode },
|
|
99
120
|
});
|
|
100
121
|
}
|
|
101
|
-
this.defaultChangeMode = options.defaultChangeMode;
|
|
122
|
+
this.defaultChangeMode = options.defaultChangeMode ?? undefined;
|
|
102
123
|
this.user = options.user;
|
|
103
|
-
this.documentRpcEnabled = documentRpcEnabled(options.env);
|
|
104
|
-
if (this.processMode === 'document' && !this.documentRpcEnabled) {
|
|
105
|
-
throw new SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN requires SUPERDOC_SDK_DOCUMENT_RPC=1.', {
|
|
106
|
-
code: 'INVALID_ARGUMENT',
|
|
107
|
-
});
|
|
108
|
-
}
|
|
124
|
+
this.documentRpcEnabled = this.processMode === 'document' || documentRpcEnabled(options.env);
|
|
109
125
|
}
|
|
110
126
|
async connect() {
|
|
111
|
-
await this.ensureConnected();
|
|
127
|
+
await this.runWhileActive(() => this.ensureConnected());
|
|
112
128
|
}
|
|
113
129
|
async dispose() {
|
|
114
|
-
if (
|
|
130
|
+
if (this.disposePromise)
|
|
131
|
+
return this.disposePromise;
|
|
132
|
+
if (this.terminalDisposalError)
|
|
133
|
+
throw this.terminalDisposalError;
|
|
134
|
+
if (!this.child && !this.connecting && this.activeCalls.size === 0)
|
|
115
135
|
return;
|
|
116
136
|
this.stopping = true;
|
|
137
|
+
const disposal = this.disposeAfterActiveCalls();
|
|
138
|
+
this.disposePromise = disposal;
|
|
139
|
+
try {
|
|
140
|
+
await disposal;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
if (this.disposePromise === disposal)
|
|
144
|
+
this.disposePromise = null;
|
|
145
|
+
this.stopping = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async disposeAfterActiveCalls() {
|
|
149
|
+
await Promise.allSettled(Array.from(this.activeCalls));
|
|
150
|
+
if (this.terminalDisposalError)
|
|
151
|
+
throw this.terminalDisposalError;
|
|
152
|
+
if (!this.child)
|
|
153
|
+
return;
|
|
154
|
+
try {
|
|
155
|
+
await this.reconcileIndeterminateDiscardCloses();
|
|
156
|
+
await this.persistDirtyDocumentRpcSessions();
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (!this.child && error instanceof SuperDocCliError) {
|
|
160
|
+
this.terminalDisposalError = error;
|
|
161
|
+
}
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
117
164
|
const child = this.child;
|
|
118
165
|
try {
|
|
119
166
|
await this.sendJsonRpcRequest('host.shutdown', {}, this.shutdownTimeoutMs);
|
|
@@ -132,29 +179,80 @@ export class HostTransport {
|
|
|
132
179
|
});
|
|
133
180
|
});
|
|
134
181
|
this.cleanupProcess(null);
|
|
135
|
-
this.stopping = false;
|
|
136
182
|
}
|
|
137
183
|
async invoke(operation, params = {}, options = {}) {
|
|
184
|
+
return this.runWhileActive(() => this.invokeWhileActive(operation, params, options));
|
|
185
|
+
}
|
|
186
|
+
async invokeWhileActive(operation, params, options) {
|
|
138
187
|
await this.ensureConnected();
|
|
188
|
+
const shouldOpenThroughCliTimeoutFallback = this.processMode === 'cli' &&
|
|
189
|
+
(this.requestTimeoutMs !== undefined ||
|
|
190
|
+
(options.timeoutMs !== undefined && !this.hostFeatures.has(DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE)));
|
|
139
191
|
if (this.documentRpcEnabled &&
|
|
140
192
|
supportsDocumentRpc(this.hostFeatures) &&
|
|
141
|
-
this.defaultChangeMode === undefined &&
|
|
142
|
-
this.user === undefined &&
|
|
193
|
+
(this.defaultChangeMode === undefined || this.hostFeatures.has(DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE)) &&
|
|
143
194
|
operation.operationId === 'doc.open' &&
|
|
144
|
-
canOpenWithDocumentRpc(params, options)
|
|
145
|
-
|
|
146
|
-
const
|
|
195
|
+
canOpenWithDocumentRpc(params, options) &&
|
|
196
|
+
!shouldOpenThroughCliTimeoutFallback) {
|
|
197
|
+
const opened = buildDocumentOpenParams(params, this.user, this.defaultChangeMode);
|
|
198
|
+
const requestTimeoutMs = this.resolveDocumentRequestTimeout(options.timeoutMs);
|
|
199
|
+
const response = await this.sendJsonRpcRequest('document.open', opened.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
|
|
147
200
|
const result = mapDocumentOpenResult(response, opened.sessionId, params.doc);
|
|
148
201
|
this.documentRpcSessions.add(opened.sessionId);
|
|
202
|
+
this.indeterminateDiscardCloses.delete(opened.sessionId);
|
|
149
203
|
return result;
|
|
150
204
|
}
|
|
151
205
|
const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
|
|
152
206
|
if (sessionId !== undefined && this.documentRpcSessions.has(sessionId)) {
|
|
153
|
-
const request = buildDocumentInvokeParams(sessionId, operation, params, options);
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
207
|
+
const request = buildDocumentInvokeParams(sessionId, operation, params, options, this.hostFeatures);
|
|
208
|
+
const supportsResponseTimeout = documentRpcRequestSupportsResponseTimeout(operation, request);
|
|
209
|
+
const requestTimeoutMs = supportsResponseTimeout
|
|
210
|
+
? this.resolveDocumentRequestTimeout(options.timeoutMs)
|
|
211
|
+
: undefined;
|
|
212
|
+
const documentOperation = operation;
|
|
213
|
+
const requestOptions = typeof request.params.options === 'object' &&
|
|
214
|
+
request.params.options !== null &&
|
|
215
|
+
!Array.isArray(request.params.options)
|
|
216
|
+
? request.params.options
|
|
217
|
+
: undefined;
|
|
218
|
+
const tracksMutation = request.method === 'document.invoke' && documentOperation.mutates === true && requestOptions?.dryRun !== true;
|
|
219
|
+
if (tracksMutation)
|
|
220
|
+
this.beginDocumentRpcMutation(sessionId);
|
|
221
|
+
let result;
|
|
222
|
+
try {
|
|
223
|
+
const response = await this.sendJsonRpcRequest(request.method, request.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
|
|
224
|
+
result = mapDocumentLifecycleResult(operation.operationId, response, sessionId, operation.operationId === 'doc.save' ? request.params.path === undefined : undefined);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (tracksMutation) {
|
|
228
|
+
const indeterminate = error instanceof SuperDocCliError && ['TIMEOUT', 'HOST_TIMEOUT', 'HOST_DISCONNECTED'].includes(error.code);
|
|
229
|
+
this.settleDocumentRpcMutation(sessionId, indeterminate ? 'indeterminate' : 'not-applied');
|
|
230
|
+
}
|
|
231
|
+
if (operation.operationId === 'doc.close' &&
|
|
232
|
+
request.params.discard === true &&
|
|
233
|
+
error instanceof SuperDocCliError &&
|
|
234
|
+
error.code === 'TIMEOUT') {
|
|
235
|
+
this.indeterminateDiscardCloses.add(sessionId);
|
|
236
|
+
}
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
if (tracksMutation) {
|
|
240
|
+
const failedReceipt = typeof result === 'object' &&
|
|
241
|
+
result !== null &&
|
|
242
|
+
!Array.isArray(result) &&
|
|
243
|
+
result.success === false;
|
|
244
|
+
this.settleDocumentRpcMutation(sessionId, failedReceipt ? 'not-applied' : 'applied');
|
|
245
|
+
}
|
|
246
|
+
if (operation.operationId === 'doc.close') {
|
|
157
247
|
this.documentRpcSessions.delete(sessionId);
|
|
248
|
+
this.clearDirtyDocumentRpcSession(sessionId);
|
|
249
|
+
this.indeterminateDiscardCloses.delete(sessionId);
|
|
250
|
+
}
|
|
251
|
+
else if (operation.operationId === 'doc.save') {
|
|
252
|
+
if (request.params.mode === undefined || request.params.mode === 'review-preserving') {
|
|
253
|
+
this.markDocumentRpcSessionSaved(sessionId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
158
256
|
return result;
|
|
159
257
|
}
|
|
160
258
|
if (this.processMode === 'document') {
|
|
@@ -163,6 +261,18 @@ export class HostTransport {
|
|
|
163
261
|
details: { operationId: operation.operationId },
|
|
164
262
|
});
|
|
165
263
|
}
|
|
264
|
+
const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
|
|
265
|
+
if (options.timeoutMs !== undefined && options.timeoutMs > cliHostRequestTimeoutMs) {
|
|
266
|
+
throw new SuperDocCliError(`CLI host cannot honor timeoutMs=${options.timeoutMs} above its ${cliHostRequestTimeoutMs}ms request ceiling.`, {
|
|
267
|
+
code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
|
|
268
|
+
details: {
|
|
269
|
+
feature: DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE,
|
|
270
|
+
operationId: operation.operationId,
|
|
271
|
+
timeoutMs: options.timeoutMs,
|
|
272
|
+
hostRequestTimeoutMs: cliHostRequestTimeoutMs,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
}
|
|
166
276
|
const argv = buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
|
|
167
277
|
const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
|
|
168
278
|
const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
|
|
@@ -177,7 +287,21 @@ export class HostTransport {
|
|
|
177
287
|
});
|
|
178
288
|
}
|
|
179
289
|
const resultRecord = response;
|
|
180
|
-
return resultRecord.data;
|
|
290
|
+
return mapCliInvocationResult(operation, resultRecord.data);
|
|
291
|
+
}
|
|
292
|
+
runWhileActive(start) {
|
|
293
|
+
if (this.terminalDisposalError) {
|
|
294
|
+
return Promise.reject(this.terminalDisposalError);
|
|
295
|
+
}
|
|
296
|
+
if (this.stopping) {
|
|
297
|
+
return Promise.reject(new SuperDocCliError('Host is disposing.', {
|
|
298
|
+
code: 'HOST_DISCONNECTED',
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
const activeCall = start();
|
|
302
|
+
this.activeCalls.add(activeCall);
|
|
303
|
+
void activeCall.then(() => this.activeCalls.delete(activeCall), () => this.activeCalls.delete(activeCall));
|
|
304
|
+
return activeCall;
|
|
181
305
|
}
|
|
182
306
|
async ensureConnected() {
|
|
183
307
|
if (this.connecting) {
|
|
@@ -203,7 +327,7 @@ export class HostTransport {
|
|
|
203
327
|
const child = spawn(command, args, {
|
|
204
328
|
env: {
|
|
205
329
|
...process.env,
|
|
206
|
-
...
|
|
330
|
+
...this.env,
|
|
207
331
|
},
|
|
208
332
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
209
333
|
});
|
|
@@ -231,7 +355,7 @@ export class HostTransport {
|
|
|
231
355
|
}));
|
|
232
356
|
});
|
|
233
357
|
child.on('close', (code, signal) => {
|
|
234
|
-
if (this.stopping) {
|
|
358
|
+
if (this.stopping && this.dirtyDocumentRpcSessions.size === 0) {
|
|
235
359
|
this.cleanupProcess(null);
|
|
236
360
|
return;
|
|
237
361
|
}
|
|
@@ -282,7 +406,13 @@ export class HostTransport {
|
|
|
282
406
|
details: { features },
|
|
283
407
|
});
|
|
284
408
|
}
|
|
285
|
-
const requiredFeatures = this.processMode === 'document'
|
|
409
|
+
const requiredFeatures = this.processMode === 'document'
|
|
410
|
+
? [
|
|
411
|
+
...DOCUMENT_HOST_REQUIRED_FEATURES,
|
|
412
|
+
...(this.defaultChangeMode === undefined ? [] : [DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE]),
|
|
413
|
+
...(this.requestTimeoutMs === undefined ? [] : [DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE]),
|
|
414
|
+
]
|
|
415
|
+
: CLI_HOST_REQUIRED_FEATURES;
|
|
286
416
|
for (const requiredFeature of requiredFeatures) {
|
|
287
417
|
if (!features.includes(requiredFeature)) {
|
|
288
418
|
throw new SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
|
|
@@ -298,7 +428,79 @@ export class HostTransport {
|
|
|
298
428
|
resolveWatchdogTimeout(timeoutMsOverride) {
|
|
299
429
|
return resolveJsWatchdogTimeout(this.watchdogTimeoutMs, this.requestTimeoutMs, timeoutMsOverride);
|
|
300
430
|
}
|
|
301
|
-
|
|
431
|
+
resolveDocumentRequestTimeout(timeoutMsOverride) {
|
|
432
|
+
const requestTimeoutMs = timeoutMsOverride ?? this.requestTimeoutMs;
|
|
433
|
+
if (requestTimeoutMs === undefined)
|
|
434
|
+
return undefined;
|
|
435
|
+
if (this.hostFeatures.has(DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE))
|
|
436
|
+
return requestTimeoutMs;
|
|
437
|
+
if (this.processMode === 'cli' && timeoutMsOverride === undefined)
|
|
438
|
+
return undefined;
|
|
439
|
+
throw new SuperDocCliError(`Structured document RPC host does not support ${DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE}.`, {
|
|
440
|
+
code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
|
|
441
|
+
details: { feature: DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE },
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
beginDocumentRpcMutation(sessionId) {
|
|
445
|
+
const state = this.documentRpcMutationStates.get(sessionId) ?? {
|
|
446
|
+
confirmedDirty: this.dirtyDocumentRpcSessions.has(sessionId),
|
|
447
|
+
pendingCount: 0,
|
|
448
|
+
};
|
|
449
|
+
state.pendingCount += 1;
|
|
450
|
+
this.documentRpcMutationStates.set(sessionId, state);
|
|
451
|
+
this.dirtyDocumentRpcSessions.add(sessionId);
|
|
452
|
+
}
|
|
453
|
+
settleDocumentRpcMutation(sessionId, outcome) {
|
|
454
|
+
const state = this.documentRpcMutationStates.get(sessionId);
|
|
455
|
+
if (!state)
|
|
456
|
+
return;
|
|
457
|
+
state.pendingCount = Math.max(0, state.pendingCount - 1);
|
|
458
|
+
if (outcome !== 'not-applied')
|
|
459
|
+
state.confirmedDirty = true;
|
|
460
|
+
if (state.confirmedDirty || state.pendingCount > 0) {
|
|
461
|
+
this.dirtyDocumentRpcSessions.add(sessionId);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
this.clearDirtyDocumentRpcSession(sessionId);
|
|
465
|
+
}
|
|
466
|
+
clearDirtyDocumentRpcSession(sessionId) {
|
|
467
|
+
this.dirtyDocumentRpcSessions.delete(sessionId);
|
|
468
|
+
this.documentRpcMutationStates.delete(sessionId);
|
|
469
|
+
}
|
|
470
|
+
markDocumentRpcSessionSaved(sessionId) {
|
|
471
|
+
const state = this.documentRpcMutationStates.get(sessionId);
|
|
472
|
+
if (!state || state.pendingCount === 0) {
|
|
473
|
+
this.clearDirtyDocumentRpcSession(sessionId);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
state.confirmedDirty = false;
|
|
477
|
+
this.dirtyDocumentRpcSessions.add(sessionId);
|
|
478
|
+
}
|
|
479
|
+
async persistDirtyDocumentRpcSessions() {
|
|
480
|
+
if (this.dirtyDocumentRpcSessions.size === 0)
|
|
481
|
+
return;
|
|
482
|
+
if (!this.hostFeatures.has(DOCUMENT_RPC_SOURCE_SAVE_FEATURE)) {
|
|
483
|
+
throw new SuperDocCliError('Document host cannot persist dirty sessions during client disposal because it does not support source saves.', {
|
|
484
|
+
code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
|
|
485
|
+
details: { feature: DOCUMENT_RPC_SOURCE_SAVE_FEATURE },
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
for (const sessionId of this.dirtyDocumentRpcSessions) {
|
|
489
|
+
const response = await this.sendJsonRpcRequest('document.save', { sessionId }, this.resolveWatchdogTimeout(undefined));
|
|
490
|
+
mapDocumentLifecycleResult('doc.save', response, sessionId, true);
|
|
491
|
+
this.markDocumentRpcSessionSaved(sessionId);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async reconcileIndeterminateDiscardCloses() {
|
|
495
|
+
for (const sessionId of this.indeterminateDiscardCloses) {
|
|
496
|
+
const response = await this.sendJsonRpcRequest('document.close', { sessionId, discard: true }, this.resolveWatchdogTimeout(undefined));
|
|
497
|
+
mapDocumentLifecycleResult('doc.close', response, sessionId);
|
|
498
|
+
this.documentRpcSessions.delete(sessionId);
|
|
499
|
+
this.clearDirtyDocumentRpcSession(sessionId);
|
|
500
|
+
this.indeterminateDiscardCloses.delete(sessionId);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async sendJsonRpcRequest(method, params, watchdogTimeoutMs, metadata = {}) {
|
|
302
504
|
const child = this.child;
|
|
303
505
|
if (!child || !child.stdin.writable) {
|
|
304
506
|
throw new SuperDocCliError('Host process is not available.', {
|
|
@@ -318,6 +520,7 @@ export class HostTransport {
|
|
|
318
520
|
id,
|
|
319
521
|
method,
|
|
320
522
|
params,
|
|
523
|
+
...(metadata.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: metadata.requestTimeoutMs }),
|
|
321
524
|
});
|
|
322
525
|
const promise = new Promise((resolve, reject) => {
|
|
323
526
|
const timer = setTimeout(() => {
|
|
@@ -428,8 +631,17 @@ export class HostTransport {
|
|
|
428
631
|
this.cleanupProcess(error);
|
|
429
632
|
}
|
|
430
633
|
cleanupProcess(error) {
|
|
634
|
+
if (error && this.dirtyDocumentRpcSessions.size > 0 && !this.terminalDisposalError) {
|
|
635
|
+
this.terminalDisposalError = new SuperDocCliError('Document host disconnected before dirty sessions could be persisted.', {
|
|
636
|
+
code: 'HOST_DISCONNECTED',
|
|
637
|
+
details: { causeCode: error.code, causeDetails: error.details },
|
|
638
|
+
});
|
|
639
|
+
}
|
|
431
640
|
this.hostFeatures.clear();
|
|
432
641
|
this.documentRpcSessions.clear();
|
|
642
|
+
this.dirtyDocumentRpcSessions.clear();
|
|
643
|
+
this.documentRpcMutationStates.clear();
|
|
644
|
+
this.indeterminateDiscardCloses.clear();
|
|
433
645
|
const child = this.child;
|
|
434
646
|
if (child) {
|
|
435
647
|
child.removeAllListeners();
|
package/dist/runtime/process.cjs
CHANGED
|
@@ -2,9 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
var host = require('./host.cjs');
|
|
4
4
|
var embeddedCli = require('./embedded-cli.cjs');
|
|
5
|
+
var embeddedDocumentHost = require('./embedded-document-host.cjs');
|
|
5
6
|
var debugTrace = require('./debug-trace.cjs');
|
|
6
7
|
var errors = require('./errors.cjs');
|
|
7
8
|
|
|
9
|
+
function resolveRuntimeProcess(options = {}, embedded = {
|
|
10
|
+
cli: embeddedCli.resolveEmbeddedCliBinary,
|
|
11
|
+
documentHost: embeddedDocumentHost.resolveEmbeddedDocumentHostBinary,
|
|
12
|
+
}) {
|
|
13
|
+
const configuredDocumentHostPath = options.documentHostPath;
|
|
14
|
+
if (configuredDocumentHostPath !== undefined && configuredDocumentHostPath.trim().length === 0) {
|
|
15
|
+
throw new errors.SuperDocCliError('documentHostPath must be a non-empty path.', {
|
|
16
|
+
code: 'INVALID_ARGUMENT',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const configuredDocumentHostBin = options.env?.SUPERDOC_SDK_DOCUMENT_HOST_BIN ?? process.env.SUPERDOC_SDK_DOCUMENT_HOST_BIN;
|
|
20
|
+
const documentHostBin = configuredDocumentHostPath ??
|
|
21
|
+
(configuredDocumentHostBin === 'embedded' ? embedded.documentHost() : configuredDocumentHostBin);
|
|
22
|
+
if (documentHostBin !== undefined && documentHostBin.trim().length === 0) {
|
|
23
|
+
throw new errors.SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN must be a non-empty path.', {
|
|
24
|
+
code: 'INVALID_ARGUMENT',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (documentHostBin !== undefined) {
|
|
28
|
+
return { hostBin: documentHostBin, processMode: 'document' };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
hostBin: options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? embedded.cli(),
|
|
32
|
+
processMode: 'cli',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
8
35
|
/**
|
|
9
36
|
* Internal runtime that delegates operations to a persistent host transport.
|
|
10
37
|
*
|
|
@@ -14,17 +41,10 @@ var errors = require('./errors.cjs');
|
|
|
14
41
|
class SuperDocRuntime {
|
|
15
42
|
transport;
|
|
16
43
|
constructor(options = {}) {
|
|
17
|
-
const
|
|
18
|
-
if (documentHostBin !== undefined && documentHostBin.trim().length === 0) {
|
|
19
|
-
throw new errors.SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN must be a non-empty path.', {
|
|
20
|
-
code: 'INVALID_ARGUMENT',
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
const hostBin = documentHostBin ?? options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? embeddedCli.resolveEmbeddedCliBinary();
|
|
44
|
+
const runtimeProcess = resolveRuntimeProcess(options);
|
|
24
45
|
this.transport = new host.HostTransport({
|
|
25
46
|
...options,
|
|
26
|
-
|
|
27
|
-
processMode: documentHostBin === undefined ? 'cli' : 'document',
|
|
47
|
+
...runtimeProcess,
|
|
28
48
|
});
|
|
29
49
|
}
|
|
30
50
|
async connect() {
|
|
@@ -67,3 +87,4 @@ class SuperDocRuntime {
|
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
exports.SuperDocRuntime = SuperDocRuntime;
|
|
90
|
+
exports.resolveRuntimeProcess = resolveRuntimeProcess;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { DocumentRuntimeKind, InvokeOptions, OperationParamSpec, OperationSpec, RuntimeInvoker, SuperDocClientOptions } from './transport-common.js';
|
|
2
|
+
type RuntimeProcessResolution = {
|
|
3
|
+
hostBin: string;
|
|
4
|
+
processMode: 'cli' | 'document';
|
|
5
|
+
};
|
|
6
|
+
type EmbeddedRuntimeResolvers = {
|
|
7
|
+
cli: () => string;
|
|
8
|
+
documentHost: () => string;
|
|
9
|
+
};
|
|
10
|
+
export declare function resolveRuntimeProcess(options?: SuperDocClientOptions, embedded?: EmbeddedRuntimeResolvers): RuntimeProcessResolution;
|
|
2
11
|
/**
|
|
3
12
|
* Internal runtime that delegates operations to a persistent host transport.
|
|
4
13
|
*
|
package/dist/runtime/process.js
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { HostTransport } from './host.js';
|
|
2
2
|
import { resolveEmbeddedCliBinary } from './embedded-cli.js';
|
|
3
|
+
import { resolveEmbeddedDocumentHostBinary } from './embedded-document-host.js';
|
|
3
4
|
import { writeSdkDebugTrace } from './debug-trace.js';
|
|
4
5
|
import { SuperDocCliError } from './errors.js';
|
|
6
|
+
export function resolveRuntimeProcess(options = {}, embedded = {
|
|
7
|
+
cli: resolveEmbeddedCliBinary,
|
|
8
|
+
documentHost: resolveEmbeddedDocumentHostBinary,
|
|
9
|
+
}) {
|
|
10
|
+
const configuredDocumentHostPath = options.documentHostPath;
|
|
11
|
+
if (configuredDocumentHostPath !== undefined && configuredDocumentHostPath.trim().length === 0) {
|
|
12
|
+
throw new SuperDocCliError('documentHostPath must be a non-empty path.', {
|
|
13
|
+
code: 'INVALID_ARGUMENT',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const configuredDocumentHostBin = options.env?.SUPERDOC_SDK_DOCUMENT_HOST_BIN ?? process.env.SUPERDOC_SDK_DOCUMENT_HOST_BIN;
|
|
17
|
+
const documentHostBin = configuredDocumentHostPath ??
|
|
18
|
+
(configuredDocumentHostBin === 'embedded' ? embedded.documentHost() : configuredDocumentHostBin);
|
|
19
|
+
if (documentHostBin !== undefined && documentHostBin.trim().length === 0) {
|
|
20
|
+
throw new SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN must be a non-empty path.', {
|
|
21
|
+
code: 'INVALID_ARGUMENT',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (documentHostBin !== undefined) {
|
|
25
|
+
return { hostBin: documentHostBin, processMode: 'document' };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
hostBin: options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? embedded.cli(),
|
|
29
|
+
processMode: 'cli',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
5
32
|
/**
|
|
6
33
|
* Internal runtime that delegates operations to a persistent host transport.
|
|
7
34
|
*
|
|
@@ -11,17 +38,10 @@ import { SuperDocCliError } from './errors.js';
|
|
|
11
38
|
export class SuperDocRuntime {
|
|
12
39
|
transport;
|
|
13
40
|
constructor(options = {}) {
|
|
14
|
-
const
|
|
15
|
-
if (documentHostBin !== undefined && documentHostBin.trim().length === 0) {
|
|
16
|
-
throw new SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN must be a non-empty path.', {
|
|
17
|
-
code: 'INVALID_ARGUMENT',
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
const hostBin = documentHostBin ?? options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? resolveEmbeddedCliBinary();
|
|
41
|
+
const runtimeProcess = resolveRuntimeProcess(options);
|
|
21
42
|
this.transport = new HostTransport({
|
|
22
43
|
...options,
|
|
23
|
-
|
|
24
|
-
processMode: documentHostBin === undefined ? 'cli' : 'document',
|
|
44
|
+
...runtimeProcess,
|
|
25
45
|
});
|
|
26
46
|
}
|
|
27
47
|
async connect() {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
|
|
4
|
+
// Source of truth: package.json. Regenerated on every SDK build so the
|
|
5
|
+
// SDK retains its own version identity when bundled into another package.
|
|
6
|
+
const SDK_VERSION = '2.8.0';
|
|
7
|
+
|
|
8
|
+
exports.SDK_VERSION = SDK_VERSION;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SDK_VERSION = "2.8.0";
|
|
@@ -163,5 +163,6 @@ function buildOperationArgv(operation, params, options, runtimeTimeoutMs, defaul
|
|
|
163
163
|
return argv;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
exports.applyOperationParamAliases = applyOperationParamAliases;
|
|
166
167
|
exports.buildOperationArgv = buildOperationArgv;
|
|
167
168
|
exports.resolveInvocation = resolveInvocation;
|
|
@@ -34,6 +34,14 @@ export interface UserIdentity {
|
|
|
34
34
|
}
|
|
35
35
|
export interface SuperDocClientOptions {
|
|
36
36
|
env?: Record<string, string | undefined>;
|
|
37
|
+
/**
|
|
38
|
+
* Standalone document-host entry to use instead of the embedded CLI.
|
|
39
|
+
*
|
|
40
|
+
* The host-only path supports local lifecycle methods and generated Document
|
|
41
|
+
* API operations through structured RPC. Interface-only conveniences fail
|
|
42
|
+
* without CLI fallback.
|
|
43
|
+
*/
|
|
44
|
+
documentHostPath?: string;
|
|
37
45
|
/**
|
|
38
46
|
* Default document engine for `client.open(...)`. Explicit `runtime` on an
|
|
39
47
|
* individual open call takes precedence.
|
|
@@ -42,11 +50,13 @@ export interface SuperDocClientOptions {
|
|
|
42
50
|
startupTimeoutMs?: number;
|
|
43
51
|
shutdownTimeoutMs?: number;
|
|
44
52
|
/**
|
|
45
|
-
* Upper bound (ms) on how long the host process may spend on a
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
53
|
+
* Upper bound (ms) on how long the host process may spend on a request
|
|
54
|
+
* before it returns a `RequestTimeout` error. Retry-safe structured document
|
|
55
|
+
* requests carry this budget as transport metadata; requests that may apply
|
|
56
|
+
* non-idempotent side effects await a definitive result instead. CLI-host
|
|
57
|
+
* requests also receive the budget through `--request-timeout-ms` at spawn.
|
|
58
|
+
* Raise this for documents that legitimately need more than 30s to process;
|
|
59
|
+
* the SDK widens its own JSON-RPC watchdog to match.
|
|
50
60
|
*
|
|
51
61
|
* Defaults to the host's own default (30s) when unset.
|
|
52
62
|
*/
|
|
@@ -54,11 +64,13 @@ export interface SuperDocClientOptions {
|
|
|
54
64
|
/**
|
|
55
65
|
* JS-side watchdog (ms) the SDK waits for a host reply before giving up.
|
|
56
66
|
* Independent of {@link requestTimeoutMs} (which controls the host-side
|
|
57
|
-
* operation budget).
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
67
|
+
* operation budget). Structured requests that may apply non-idempotent side
|
|
68
|
+
* effects wait for a definitive host result instead of applying an unsafe
|
|
69
|
+
* response deadline. Most callers should leave this at its default and use
|
|
70
|
+
* {@link requestTimeoutMs} as the single timeout knob for retry-safe work —
|
|
71
|
+
* `resolveWatchdogTimeout` already widens the watchdog above the host ceiling
|
|
72
|
+
* automatically. Override only when you need to detect a hung or crashed host
|
|
73
|
+
* faster than the operation budget allows.
|
|
62
74
|
*/
|
|
63
75
|
watchdogTimeoutMs?: number;
|
|
64
76
|
maxQueueDepth?: number;
|
|
@@ -70,6 +82,12 @@ export interface CliInvocation {
|
|
|
70
82
|
prefixArgs: string[];
|
|
71
83
|
}
|
|
72
84
|
export declare function resolveInvocation(cliBin: string): CliInvocation;
|
|
85
|
+
/**
|
|
86
|
+
* Resolve contract-shaped param aliases onto their canonical generated names.
|
|
87
|
+
* Returns the same object when no alias applies. Throws on a conflicting
|
|
88
|
+
* canonical/alias pair so divergent values can never be silently coalesced.
|
|
89
|
+
*/
|
|
90
|
+
export declare function applyOperationParamAliases(operation: OperationSpec, params: Record<string, unknown>): Record<string, unknown>;
|
|
73
91
|
/**
|
|
74
92
|
* Build the CLI argument vector for an operation invocation.
|
|
75
93
|
*
|
|
@@ -54,7 +54,7 @@ function assertKnownTemplatesApplyParams(operation, params) {
|
|
|
54
54
|
* Returns the same object when no alias applies. Throws on a conflicting
|
|
55
55
|
* canonical/alias pair so divergent values can never be silently coalesced.
|
|
56
56
|
*/
|
|
57
|
-
function applyOperationParamAliases(operation, params) {
|
|
57
|
+
export function applyOperationParamAliases(operation, params) {
|
|
58
58
|
const aliasMap = OPERATION_PARAM_ALIASES[operation.operationId];
|
|
59
59
|
if (!aliasMap)
|
|
60
60
|
return params;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superdoc/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Node SDK for SuperDoc, wrapping the SuperDoc CLI to read and edit .docx files from JavaScript and TypeScript.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -37,17 +37,17 @@
|
|
|
37
37
|
"typescript": "^5.9.2"
|
|
38
38
|
},
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@superdoc/sdk-darwin-arm64": "2.
|
|
41
|
-
"@superdoc/sdk-
|
|
42
|
-
"@superdoc/sdk-
|
|
43
|
-
"@superdoc/sdk-linux-arm64": "2.
|
|
44
|
-
"@superdoc/sdk-windows-x64": "2.
|
|
40
|
+
"@superdoc/sdk-darwin-arm64": "2.8.0",
|
|
41
|
+
"@superdoc/sdk-darwin-x64": "2.8.0",
|
|
42
|
+
"@superdoc/sdk-linux-x64": "2.8.0",
|
|
43
|
+
"@superdoc/sdk-linux-arm64": "2.8.0",
|
|
44
|
+
"@superdoc/sdk-windows-x64": "2.8.0"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
|
-
"build": "rm -rf dist && node scripts/embed-prompts.mjs && node scripts/embed-tools.mjs && tsc && rollup -c rollup.cjs.config.mjs && rm -rf dist/prompts && mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/ && pnpm run audit:publish",
|
|
50
|
+
"build": "rm -rf dist && node scripts/embed-version.mjs && node scripts/embed-prompts.mjs && node scripts/embed-tools.mjs && tsc && rollup -c rollup.cjs.config.mjs && rm -rf dist/prompts && mkdir -p dist/prompts && cp src/prompts/*.md dist/prompts/ && pnpm run audit:publish",
|
|
51
51
|
"audit:publish": "node ../../../../scripts/audit-publish-artifact.mjs dist --label sdk-node-dist",
|
|
52
52
|
"typecheck": "tsc --noEmit",
|
|
53
53
|
"test:document-host": "bun test src/runtime/__tests__/host-spawn-args.test.ts src/runtime/__tests__/document-rpc.test.ts src/__tests__/structured-document-rpc.e2e.test.ts src/__tests__/request-timeout-ms.e2e.test.ts",
|
|
Binary file
|
|
Binary file
|
package/tools/tools-policy.json
CHANGED