@superdoc/sdk 2.6.0 → 2.7.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.
Files changed (48) hide show
  1. package/dist/agent/actions.cjs +1869 -160
  2. package/dist/agent/actions.d.ts +77 -10
  3. package/dist/agent/actions.js +1870 -161
  4. package/dist/agent/catalog.cjs +102 -9
  5. package/dist/agent/catalog.d.ts +243 -0
  6. package/dist/agent/catalog.js +99 -9
  7. package/dist/agent/doc-snapshot.cjs +200 -2
  8. package/dist/agent/doc-snapshot.d.ts +91 -0
  9. package/dist/agent/doc-snapshot.js +199 -2
  10. package/dist/agent/runtime.cjs +9 -1
  11. package/dist/agent/runtime.d.ts +8 -0
  12. package/dist/agent/runtime.js +9 -1
  13. package/dist/generated/client.cjs +754 -770
  14. package/dist/generated/client.d.ts +9 -9
  15. package/dist/generated/client.js +754 -770
  16. package/dist/generated/contract.cjs +16170 -272
  17. package/dist/generated/contract.d.ts +38 -0
  18. package/dist/generated/contract.js +17411 -1510
  19. package/dist/index.cjs +5 -4
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +5 -4
  22. package/dist/introspection.cjs +59 -0
  23. package/dist/introspection.d.ts +3 -0
  24. package/dist/introspection.js +53 -0
  25. package/dist/runtime/document-rpc.cjs +179 -40
  26. package/dist/runtime/document-rpc.d.ts +13 -4
  27. package/dist/runtime/document-rpc.js +175 -40
  28. package/dist/runtime/embedded-cli.cjs +5 -68
  29. package/dist/runtime/embedded-cli.js +5 -67
  30. package/dist/runtime/embedded-document-host.cjs +28 -0
  31. package/dist/runtime/embedded-document-host.d.ts +1 -0
  32. package/dist/runtime/embedded-document-host.js +23 -0
  33. package/dist/runtime/embedded-platform.cjs +102 -0
  34. package/dist/runtime/embedded-platform.d.ts +5 -0
  35. package/dist/runtime/embedded-platform.js +93 -0
  36. package/dist/runtime/host.cjs +70 -19
  37. package/dist/runtime/host.d.ts +2 -0
  38. package/dist/runtime/host.js +70 -20
  39. package/dist/runtime/process.cjs +30 -9
  40. package/dist/runtime/process.d.ts +9 -0
  41. package/dist/runtime/process.js +29 -9
  42. package/dist/runtime/transport-common.cjs +1 -0
  43. package/dist/runtime/transport-common.d.ts +28 -10
  44. package/dist/runtime/transport-common.js +1 -1
  45. package/package.json +6 -6
  46. package/tools/__pycache__/__init__.cpython-311.pyc +0 -0
  47. package/tools/__pycache__/intent_dispatch_generated.cpython-311.pyc +0 -0
  48. package/tools/tools-policy.json +1 -1
@@ -0,0 +1,93 @@
1
+ import { chmodSync, existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const require = createRequire(import.meta.url);
6
+ const TARGET_TO_PACKAGE = {
7
+ 'darwin-arm64': '@superdoc/sdk-darwin-arm64',
8
+ 'darwin-x64': '@superdoc/sdk-darwin-x64',
9
+ 'linux-x64': '@superdoc/sdk-linux-x64',
10
+ 'linux-arm64': '@superdoc/sdk-linux-arm64',
11
+ 'windows-x64': '@superdoc/sdk-windows-x64',
12
+ };
13
+ const TARGET_TO_DIR = {
14
+ 'darwin-arm64': 'sdk-darwin-arm64',
15
+ 'darwin-x64': 'sdk-darwin-x64',
16
+ 'linux-x64': 'sdk-linux-x64',
17
+ 'linux-arm64': 'sdk-linux-arm64',
18
+ 'windows-x64': 'sdk-windows-x64',
19
+ };
20
+ export function resolveEmbeddedTarget(platform = process.platform, arch = process.arch) {
21
+ if (platform === 'darwin' && arch === 'arm64')
22
+ return 'darwin-arm64';
23
+ if (platform === 'darwin' && arch === 'x64')
24
+ return 'darwin-x64';
25
+ if (platform === 'linux' && arch === 'x64')
26
+ return 'linux-x64';
27
+ if (platform === 'linux' && arch === 'arm64')
28
+ return 'linux-arm64';
29
+ if (platform === 'win32' && arch === 'x64')
30
+ return 'windows-x64';
31
+ return null;
32
+ }
33
+ export function embeddedPlatformPackage(target) {
34
+ return TARGET_TO_PACKAGE[target];
35
+ }
36
+ /**
37
+ * The binary shipped inside this package (`platforms/<target>/bin/`). A build
38
+ * that carries its own host is authoritative — it is the only copy guaranteed
39
+ * to match this SDK's code — so it is tried before any resolver lookup.
40
+ */
41
+ function resolveFromPackageLocal(target, binaryName) {
42
+ const workspacePath = path.resolve(fileURLToPath(new URL('../../platforms', import.meta.url)), TARGET_TO_DIR[target], 'bin', binaryName);
43
+ return existsSync(workspacePath) ? workspacePath : null;
44
+ }
45
+ /**
46
+ * Resolve the platform package through Node, and reject a version that is not
47
+ * this SDK's own.
48
+ *
49
+ * `require.resolve` falls back to NODE_PATH, which package managers set to a
50
+ * flat store directory (pnpm does it for every binary shim). A host binary from
51
+ * an unrelated SDK version installed anywhere in that store then satisfies this
52
+ * lookup, and the SDK silently drives a foreign engine: seen in the wild as an
53
+ * SDK 2.2.1 install executing a hoisted 2.0.0 host, which failed operations the
54
+ * matching host performs correctly. optionalDependencies pin the platform
55
+ * package to an exact version, so a mismatch here is never legitimate.
56
+ */
57
+ function resolveFromPlatformPackage(target, binaryName) {
58
+ const pkg = TARGET_TO_PACKAGE[target];
59
+ let binaryPath;
60
+ try {
61
+ binaryPath = require.resolve(`${pkg}/bin/${binaryName}`);
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ try {
67
+ const platformVersion = require(require.resolve(`${pkg}/package.json`)).version;
68
+ const ownVersion = require(fileURLToPath(new URL('../../package.json', import.meta.url))).version;
69
+ if (platformVersion !== ownVersion)
70
+ return null;
71
+ }
72
+ catch {
73
+ // Either manifest being unreadable is itself disqualifying: an unverifiable
74
+ // host is exactly the case this guard exists to reject.
75
+ return null;
76
+ }
77
+ return binaryPath;
78
+ }
79
+ export function resolveEmbeddedPlatformBinary(target, binaryName) {
80
+ // Package-local first: a self-contained build must never be overridden by
81
+ // whatever the module resolver happens to find on NODE_PATH.
82
+ return resolveFromPackageLocal(target, binaryName) ?? resolveFromPlatformPackage(target, binaryName);
83
+ }
84
+ export function ensureEmbeddedExecutable(binaryPath) {
85
+ if (process.platform === 'win32')
86
+ return;
87
+ try {
88
+ chmodSync(binaryPath, 0o755);
89
+ }
90
+ catch {
91
+ // Non-fatal: spawn() reports the actionable execution error.
92
+ }
93
+ }
@@ -23,6 +23,21 @@ const HOST_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
23
23
  // abort. The buffer absorbs JSON-RPC serialization, stdio drain, and event-
24
24
  // loop latency.
25
25
  const WATCHDOG_HEADROOM_MS = 5_000;
26
+ function mapCliInvocationResult(operation, value) {
27
+ const envelopeKey = operation.responseEnvelopeKey;
28
+ if (envelopeKey === null || envelopeKey === undefined)
29
+ return value;
30
+ if (typeof envelopeKey !== 'string' || envelopeKey.length === 0) {
31
+ throw new errors.SuperDocCliError('Generated operation has invalid response envelope metadata.', {
32
+ code: 'HOST_PROTOCOL_ERROR',
33
+ details: { operationId: operation.operationId, responseEnvelopeKey: envelopeKey },
34
+ });
35
+ }
36
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
37
+ return value;
38
+ const extracted = value[envelopeKey];
39
+ return extracted === undefined ? value : extracted;
40
+ }
26
41
  /**
27
42
  * Builds the argv passed to `spawn` for `superdoc host --stdio`. Propagates
28
43
  * `requestTimeoutMs` to the host via `--request-timeout-ms`, since the SDK
@@ -101,14 +116,9 @@ class HostTransport {
101
116
  details: { defaultChangeMode: options.defaultChangeMode },
102
117
  });
103
118
  }
104
- this.defaultChangeMode = options.defaultChangeMode;
119
+ this.defaultChangeMode = options.defaultChangeMode ?? undefined;
105
120
  this.user = options.user;
106
- this.documentRpcEnabled = documentRpc.documentRpcEnabled(options.env);
107
- if (this.processMode === 'document' && !this.documentRpcEnabled) {
108
- throw new errors.SuperDocCliError('SUPERDOC_SDK_DOCUMENT_HOST_BIN requires SUPERDOC_SDK_DOCUMENT_RPC=1.', {
109
- code: 'INVALID_ARGUMENT',
110
- });
111
- }
121
+ this.documentRpcEnabled = this.processMode === 'document' || documentRpc.documentRpcEnabled(options.env);
112
122
  }
113
123
  async connect() {
114
124
  await this.ensureConnected();
@@ -139,23 +149,31 @@ class HostTransport {
139
149
  }
140
150
  async invoke(operation, params = {}, options = {}) {
141
151
  await this.ensureConnected();
152
+ const shouldOpenThroughCliTimeoutFallback = this.processMode === 'cli' &&
153
+ (this.requestTimeoutMs !== undefined ||
154
+ (options.timeoutMs !== undefined && !this.hostFeatures.has(documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE)));
142
155
  if (this.documentRpcEnabled &&
143
156
  documentRpc.supportsDocumentRpc(this.hostFeatures) &&
144
- this.defaultChangeMode === undefined &&
145
- this.user === undefined &&
157
+ (this.defaultChangeMode === undefined || this.hostFeatures.has(documentRpc.DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE)) &&
146
158
  operation.operationId === 'doc.open' &&
147
- documentRpc.canOpenWithDocumentRpc(params, options)) {
148
- const opened = documentRpc.buildDocumentOpenParams(params);
149
- const response = await this.sendJsonRpcRequest('document.open', opened.params, this.resolveWatchdogTimeout(options.timeoutMs));
159
+ documentRpc.canOpenWithDocumentRpc(params, options) &&
160
+ !shouldOpenThroughCliTimeoutFallback) {
161
+ const opened = documentRpc.buildDocumentOpenParams(params, this.user, this.defaultChangeMode);
162
+ const requestTimeoutMs = this.resolveDocumentRequestTimeout(options.timeoutMs);
163
+ const response = await this.sendJsonRpcRequest('document.open', opened.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
150
164
  const result = documentRpc.mapDocumentOpenResult(response, opened.sessionId, params.doc);
151
165
  this.documentRpcSessions.add(opened.sessionId);
152
166
  return result;
153
167
  }
154
168
  const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
155
169
  if (sessionId !== undefined && this.documentRpcSessions.has(sessionId)) {
156
- const request = documentRpc.buildDocumentInvokeParams(sessionId, operation, params, options);
157
- const response = await this.sendJsonRpcRequest(request.method, request.params, this.resolveWatchdogTimeout(options.timeoutMs));
158
- const result = documentRpc.mapDocumentLifecycleResult(operation.operationId, response, sessionId);
170
+ const request = documentRpc.buildDocumentInvokeParams(sessionId, operation, params, options, this.hostFeatures);
171
+ const supportsResponseTimeout = documentRpc.documentRpcRequestSupportsResponseTimeout(operation, request);
172
+ const requestTimeoutMs = supportsResponseTimeout
173
+ ? this.resolveDocumentRequestTimeout(options.timeoutMs)
174
+ : undefined;
175
+ const response = await this.sendJsonRpcRequest(request.method, request.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
176
+ const result = documentRpc.mapDocumentLifecycleResult(operation.operationId, response, sessionId, operation.operationId === 'doc.save' ? request.params.path === undefined : undefined);
159
177
  if (operation.operationId === 'doc.close')
160
178
  this.documentRpcSessions.delete(sessionId);
161
179
  return result;
@@ -166,6 +184,18 @@ class HostTransport {
166
184
  details: { operationId: operation.operationId },
167
185
  });
168
186
  }
187
+ const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
188
+ if (options.timeoutMs !== undefined && options.timeoutMs > cliHostRequestTimeoutMs) {
189
+ throw new errors.SuperDocCliError(`CLI host cannot honor timeoutMs=${options.timeoutMs} above its ${cliHostRequestTimeoutMs}ms request ceiling.`, {
190
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
191
+ details: {
192
+ feature: documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE,
193
+ operationId: operation.operationId,
194
+ timeoutMs: options.timeoutMs,
195
+ hostRequestTimeoutMs: cliHostRequestTimeoutMs,
196
+ },
197
+ });
198
+ }
169
199
  const argv = transportCommon.buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
170
200
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
171
201
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
@@ -180,7 +210,7 @@ class HostTransport {
180
210
  });
181
211
  }
182
212
  const resultRecord = response;
183
- return resultRecord.data;
213
+ return mapCliInvocationResult(operation, resultRecord.data);
184
214
  }
185
215
  async ensureConnected() {
186
216
  if (this.connecting) {
@@ -206,7 +236,7 @@ class HostTransport {
206
236
  const child = node_child_process.spawn(command, args, {
207
237
  env: {
208
238
  ...process.env,
209
- ...(this.env ?? {}),
239
+ ...this.env,
210
240
  },
211
241
  stdio: ['pipe', 'pipe', 'pipe'],
212
242
  });
@@ -285,7 +315,13 @@ class HostTransport {
285
315
  details: { features },
286
316
  });
287
317
  }
288
- const requiredFeatures = this.processMode === 'document' ? DOCUMENT_HOST_REQUIRED_FEATURES : CLI_HOST_REQUIRED_FEATURES;
318
+ const requiredFeatures = this.processMode === 'document'
319
+ ? [
320
+ ...DOCUMENT_HOST_REQUIRED_FEATURES,
321
+ ...(this.defaultChangeMode === undefined ? [] : [documentRpc.DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE]),
322
+ ...(this.requestTimeoutMs === undefined ? [] : [documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE]),
323
+ ]
324
+ : CLI_HOST_REQUIRED_FEATURES;
289
325
  for (const requiredFeature of requiredFeatures) {
290
326
  if (!features.includes(requiredFeature)) {
291
327
  throw new errors.SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
@@ -301,7 +337,20 @@ class HostTransport {
301
337
  resolveWatchdogTimeout(timeoutMsOverride) {
302
338
  return resolveJsWatchdogTimeout(this.watchdogTimeoutMs, this.requestTimeoutMs, timeoutMsOverride);
303
339
  }
304
- async sendJsonRpcRequest(method, params, watchdogTimeoutMs) {
340
+ resolveDocumentRequestTimeout(timeoutMsOverride) {
341
+ const requestTimeoutMs = timeoutMsOverride ?? this.requestTimeoutMs;
342
+ if (requestTimeoutMs === undefined)
343
+ return undefined;
344
+ if (this.hostFeatures.has(documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE))
345
+ return requestTimeoutMs;
346
+ if (this.processMode === 'cli' && timeoutMsOverride === undefined)
347
+ return undefined;
348
+ throw new errors.SuperDocCliError(`Structured document RPC host does not support ${documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE}.`, {
349
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
350
+ details: { feature: documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE },
351
+ });
352
+ }
353
+ async sendJsonRpcRequest(method, params, watchdogTimeoutMs, metadata = {}) {
305
354
  const child = this.child;
306
355
  if (!child || !child.stdin.writable) {
307
356
  throw new errors.SuperDocCliError('Host process is not available.', {
@@ -321,6 +370,7 @@ class HostTransport {
321
370
  id,
322
371
  method,
323
372
  params,
373
+ ...(metadata.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: metadata.requestTimeoutMs }),
324
374
  });
325
375
  const promise = new Promise((resolve, reject) => {
326
376
  const timer = setTimeout(() => {
@@ -460,4 +510,5 @@ class HostTransport {
460
510
  exports.HostTransport = HostTransport;
461
511
  exports.buildDocumentHostSpawnArgs = buildDocumentHostSpawnArgs;
462
512
  exports.buildHostSpawnArgs = buildHostSpawnArgs;
513
+ exports.mapCliInvocationResult = mapCliInvocationResult;
463
514
  exports.resolveJsWatchdogTimeout = resolveJsWatchdogTimeout;
@@ -1,4 +1,5 @@
1
1
  import { type InvokeOptions, type OperationSpec, type SuperDocClientOptions } from './transport-common.js';
2
+ export declare function mapCliInvocationResult(operation: OperationSpec, value: unknown): unknown;
2
3
  /**
3
4
  * Builds the argv passed to `spawn` for `superdoc host --stdio`. Propagates
4
5
  * `requestTimeoutMs` to the host via `--request-timeout-ms`, since the SDK
@@ -59,6 +60,7 @@ export declare class HostTransport {
59
60
  private startHostProcess;
60
61
  private assertCapabilities;
61
62
  private resolveWatchdogTimeout;
63
+ private resolveDocumentRequestTimeout;
62
64
  private sendJsonRpcRequest;
63
65
  private onStdoutLine;
64
66
  private mapJsonRpcError;
@@ -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, 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
@@ -98,14 +113,9 @@ export class HostTransport {
98
113
  details: { defaultChangeMode: options.defaultChangeMode },
99
114
  });
100
115
  }
101
- this.defaultChangeMode = options.defaultChangeMode;
116
+ this.defaultChangeMode = options.defaultChangeMode ?? undefined;
102
117
  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
- }
118
+ this.documentRpcEnabled = this.processMode === 'document' || documentRpcEnabled(options.env);
109
119
  }
110
120
  async connect() {
111
121
  await this.ensureConnected();
@@ -136,23 +146,31 @@ export class HostTransport {
136
146
  }
137
147
  async invoke(operation, params = {}, options = {}) {
138
148
  await this.ensureConnected();
149
+ const shouldOpenThroughCliTimeoutFallback = this.processMode === 'cli' &&
150
+ (this.requestTimeoutMs !== undefined ||
151
+ (options.timeoutMs !== undefined && !this.hostFeatures.has(DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE)));
139
152
  if (this.documentRpcEnabled &&
140
153
  supportsDocumentRpc(this.hostFeatures) &&
141
- this.defaultChangeMode === undefined &&
142
- this.user === undefined &&
154
+ (this.defaultChangeMode === undefined || this.hostFeatures.has(DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE)) &&
143
155
  operation.operationId === 'doc.open' &&
144
- canOpenWithDocumentRpc(params, options)) {
145
- const opened = buildDocumentOpenParams(params);
146
- const response = await this.sendJsonRpcRequest('document.open', opened.params, this.resolveWatchdogTimeout(options.timeoutMs));
156
+ canOpenWithDocumentRpc(params, options) &&
157
+ !shouldOpenThroughCliTimeoutFallback) {
158
+ const opened = buildDocumentOpenParams(params, this.user, this.defaultChangeMode);
159
+ const requestTimeoutMs = this.resolveDocumentRequestTimeout(options.timeoutMs);
160
+ const response = await this.sendJsonRpcRequest('document.open', opened.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
147
161
  const result = mapDocumentOpenResult(response, opened.sessionId, params.doc);
148
162
  this.documentRpcSessions.add(opened.sessionId);
149
163
  return result;
150
164
  }
151
165
  const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
152
166
  if (sessionId !== undefined && this.documentRpcSessions.has(sessionId)) {
153
- const request = buildDocumentInvokeParams(sessionId, operation, params, options);
154
- const response = await this.sendJsonRpcRequest(request.method, request.params, this.resolveWatchdogTimeout(options.timeoutMs));
155
- const result = mapDocumentLifecycleResult(operation.operationId, response, sessionId);
167
+ const request = buildDocumentInvokeParams(sessionId, operation, params, options, this.hostFeatures);
168
+ const supportsResponseTimeout = documentRpcRequestSupportsResponseTimeout(operation, request);
169
+ const requestTimeoutMs = supportsResponseTimeout
170
+ ? this.resolveDocumentRequestTimeout(options.timeoutMs)
171
+ : undefined;
172
+ const response = await this.sendJsonRpcRequest(request.method, request.params, this.resolveWatchdogTimeout(options.timeoutMs), { requestTimeoutMs });
173
+ const result = mapDocumentLifecycleResult(operation.operationId, response, sessionId, operation.operationId === 'doc.save' ? request.params.path === undefined : undefined);
156
174
  if (operation.operationId === 'doc.close')
157
175
  this.documentRpcSessions.delete(sessionId);
158
176
  return result;
@@ -163,6 +181,18 @@ export class HostTransport {
163
181
  details: { operationId: operation.operationId },
164
182
  });
165
183
  }
184
+ const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
185
+ if (options.timeoutMs !== undefined && options.timeoutMs > cliHostRequestTimeoutMs) {
186
+ throw new SuperDocCliError(`CLI host cannot honor timeoutMs=${options.timeoutMs} above its ${cliHostRequestTimeoutMs}ms request ceiling.`, {
187
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
188
+ details: {
189
+ feature: DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE,
190
+ operationId: operation.operationId,
191
+ timeoutMs: options.timeoutMs,
192
+ hostRequestTimeoutMs: cliHostRequestTimeoutMs,
193
+ },
194
+ });
195
+ }
166
196
  const argv = buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
167
197
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
168
198
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
@@ -177,7 +207,7 @@ export class HostTransport {
177
207
  });
178
208
  }
179
209
  const resultRecord = response;
180
- return resultRecord.data;
210
+ return mapCliInvocationResult(operation, resultRecord.data);
181
211
  }
182
212
  async ensureConnected() {
183
213
  if (this.connecting) {
@@ -203,7 +233,7 @@ export class HostTransport {
203
233
  const child = spawn(command, args, {
204
234
  env: {
205
235
  ...process.env,
206
- ...(this.env ?? {}),
236
+ ...this.env,
207
237
  },
208
238
  stdio: ['pipe', 'pipe', 'pipe'],
209
239
  });
@@ -282,7 +312,13 @@ export class HostTransport {
282
312
  details: { features },
283
313
  });
284
314
  }
285
- const requiredFeatures = this.processMode === 'document' ? DOCUMENT_HOST_REQUIRED_FEATURES : CLI_HOST_REQUIRED_FEATURES;
315
+ const requiredFeatures = this.processMode === 'document'
316
+ ? [
317
+ ...DOCUMENT_HOST_REQUIRED_FEATURES,
318
+ ...(this.defaultChangeMode === undefined ? [] : [DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE]),
319
+ ...(this.requestTimeoutMs === undefined ? [] : [DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE]),
320
+ ]
321
+ : CLI_HOST_REQUIRED_FEATURES;
286
322
  for (const requiredFeature of requiredFeatures) {
287
323
  if (!features.includes(requiredFeature)) {
288
324
  throw new SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
@@ -298,7 +334,20 @@ export class HostTransport {
298
334
  resolveWatchdogTimeout(timeoutMsOverride) {
299
335
  return resolveJsWatchdogTimeout(this.watchdogTimeoutMs, this.requestTimeoutMs, timeoutMsOverride);
300
336
  }
301
- async sendJsonRpcRequest(method, params, watchdogTimeoutMs) {
337
+ resolveDocumentRequestTimeout(timeoutMsOverride) {
338
+ const requestTimeoutMs = timeoutMsOverride ?? this.requestTimeoutMs;
339
+ if (requestTimeoutMs === undefined)
340
+ return undefined;
341
+ if (this.hostFeatures.has(DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE))
342
+ return requestTimeoutMs;
343
+ if (this.processMode === 'cli' && timeoutMsOverride === undefined)
344
+ return undefined;
345
+ throw new SuperDocCliError(`Structured document RPC host does not support ${DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE}.`, {
346
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
347
+ details: { feature: DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE },
348
+ });
349
+ }
350
+ async sendJsonRpcRequest(method, params, watchdogTimeoutMs, metadata = {}) {
302
351
  const child = this.child;
303
352
  if (!child || !child.stdin.writable) {
304
353
  throw new SuperDocCliError('Host process is not available.', {
@@ -318,6 +367,7 @@ export class HostTransport {
318
367
  id,
319
368
  method,
320
369
  params,
370
+ ...(metadata.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: metadata.requestTimeoutMs }),
321
371
  });
322
372
  const promise = new Promise((resolve, reject) => {
323
373
  const timer = setTimeout(() => {
@@ -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 documentHostBin = options.env?.SUPERDOC_SDK_DOCUMENT_HOST_BIN ?? process.env.SUPERDOC_SDK_DOCUMENT_HOST_BIN;
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
- hostBin,
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
  *
@@ -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 documentHostBin = options.env?.SUPERDOC_SDK_DOCUMENT_HOST_BIN ?? process.env.SUPERDOC_SDK_DOCUMENT_HOST_BIN;
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
- hostBin,
24
- processMode: documentHostBin === undefined ? 'cli' : 'document',
44
+ ...runtimeProcess,
25
45
  });
26
46
  }
27
47
  async connect() {
@@ -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 single
46
- * `cli.invoke` request before it kills the operation and returns a
47
- * `RequestTimeout` error. Propagated to the host via `--request-timeout-ms`
48
- * at spawn. Raise this for documents that legitimately need more than 30s
49
- * to process; the SDK widens its own JSON-RPC watchdog to match.
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). Most callers should leave this at its default and use
58
- * {@link requestTimeoutMs} as the single operation-timeout knob
59
- * `resolveWatchdogTimeout` already widens the watchdog above the host
60
- * ceiling automatically. Override only when you need to detect a hung or
61
- * crashed host faster than the operation budget allows.
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;