@superdoc/sdk 2.4.1 → 2.6.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.
@@ -4,9 +4,11 @@ var node_child_process = require('node:child_process');
4
4
  var node_readline = require('node:readline');
5
5
  var transportCommon = require('./transport-common.cjs');
6
6
  var errors = require('./errors.cjs');
7
+ var documentRpc = require('./document-rpc.cjs');
7
8
 
8
9
  const HOST_PROTOCOL_VERSION = '1.0';
9
- const REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
10
+ const CLI_HOST_REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
11
+ const DOCUMENT_HOST_REQUIRED_FEATURES = [...documentRpc.DOCUMENT_RPC_FEATURES, 'host.shutdown'];
10
12
  const CHANGE_MODES = ['direct', 'tracked'];
11
13
  const FORWARD_HOST_STDERR = typeof process !== 'undefined' && typeof process.env?.SUPERDOC_DEBUG_TEXT_REWRITE === 'string'
12
14
  ? process.env.SUPERDOC_DEBUG_TEXT_REWRITE === '1'
@@ -35,6 +37,9 @@ function buildHostSpawnArgs(prefixArgs, options) {
35
37
  }
36
38
  return args;
37
39
  }
40
+ function buildDocumentHostSpawnArgs(prefixArgs) {
41
+ return [...prefixArgs];
42
+ }
38
43
  /**
39
44
  * Computes the JS-side watchdog timeout for a single JSON-RPC request.
40
45
  *
@@ -60,11 +65,10 @@ function resolveJsWatchdogTimeout(watchdogTimeoutMs, requestTimeoutMs, timeoutMs
60
65
  }
61
66
  return Math.max(watchdogTimeoutMs, HOST_DEFAULT_REQUEST_TIMEOUT_MS + WATCHDOG_HEADROOM_MS);
62
67
  }
63
- /**
64
- * Transport that communicates with a long-lived CLI host process over JSON-RPC stdio.
65
- */
68
+ /** Transport for the legacy CLI host or the structured document host. */
66
69
  class HostTransport {
67
- cliBin;
70
+ hostBin;
71
+ processMode;
68
72
  env;
69
73
  startupTimeoutMs;
70
74
  shutdownTimeoutMs;
@@ -73,14 +77,18 @@ class HostTransport {
73
77
  maxQueueDepth;
74
78
  defaultChangeMode;
75
79
  user;
80
+ documentRpcEnabled;
76
81
  child = null;
77
82
  stdoutReader = null;
78
83
  pending = new Map();
79
84
  nextRequestId = 1;
80
85
  connecting = null;
81
86
  stopping = false;
87
+ hostFeatures = new Set();
88
+ documentRpcSessions = new Set();
82
89
  constructor(options) {
83
- this.cliBin = options.cliBin;
90
+ this.hostBin = options.hostBin;
91
+ this.processMode = options.processMode ?? 'cli';
84
92
  this.env = options.env;
85
93
  this.startupTimeoutMs = options.startupTimeoutMs ?? 5_000;
86
94
  this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 5_000;
@@ -95,6 +103,12 @@ class HostTransport {
95
103
  }
96
104
  this.defaultChangeMode = options.defaultChangeMode;
97
105
  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
+ }
98
112
  }
99
113
  async connect() {
100
114
  await this.ensureConnected();
@@ -125,6 +139,33 @@ class HostTransport {
125
139
  }
126
140
  async invoke(operation, params = {}, options = {}) {
127
141
  await this.ensureConnected();
142
+ if (this.documentRpcEnabled &&
143
+ documentRpc.supportsDocumentRpc(this.hostFeatures) &&
144
+ this.defaultChangeMode === undefined &&
145
+ this.user === undefined &&
146
+ 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));
150
+ const result = documentRpc.mapDocumentOpenResult(response, opened.sessionId, params.doc);
151
+ this.documentRpcSessions.add(opened.sessionId);
152
+ return result;
153
+ }
154
+ const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
155
+ 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);
159
+ if (operation.operationId === 'doc.close')
160
+ this.documentRpcSessions.delete(sessionId);
161
+ return result;
162
+ }
163
+ if (this.processMode === 'document') {
164
+ throw new errors.SuperDocCliError(`Standalone document host does not support ${operation.operationId} through structured RPC v0.`, {
165
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
166
+ details: { operationId: operation.operationId },
167
+ });
168
+ }
128
169
  const argv = transportCommon.buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
129
170
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
130
171
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
@@ -142,13 +183,13 @@ class HostTransport {
142
183
  return resultRecord.data;
143
184
  }
144
185
  async ensureConnected() {
145
- if (this.child && !this.child.killed) {
146
- return;
147
- }
148
186
  if (this.connecting) {
149
187
  await this.connecting;
150
188
  return;
151
189
  }
190
+ if (this.child && !this.child.killed) {
191
+ return;
192
+ }
152
193
  this.connecting = this.startHostProcess();
153
194
  try {
154
195
  await this.connecting;
@@ -158,8 +199,10 @@ class HostTransport {
158
199
  }
159
200
  }
160
201
  async startHostProcess() {
161
- const { command, prefixArgs } = transportCommon.resolveInvocation(this.cliBin);
162
- const args = buildHostSpawnArgs(prefixArgs, { requestTimeoutMs: this.requestTimeoutMs });
202
+ const { command, prefixArgs } = transportCommon.resolveInvocation(this.hostBin);
203
+ const args = this.processMode === 'document'
204
+ ? buildDocumentHostSpawnArgs(prefixArgs)
205
+ : buildHostSpawnArgs(prefixArgs, { requestTimeoutMs: this.requestTimeoutMs });
163
206
  const child = node_child_process.spawn(command, args, {
164
207
  env: {
165
208
  ...process.env,
@@ -242,7 +285,8 @@ class HostTransport {
242
285
  details: { features },
243
286
  });
244
287
  }
245
- for (const requiredFeature of REQUIRED_FEATURES) {
288
+ const requiredFeatures = this.processMode === 'document' ? DOCUMENT_HOST_REQUIRED_FEATURES : CLI_HOST_REQUIRED_FEATURES;
289
+ for (const requiredFeature of requiredFeatures) {
246
290
  if (!features.includes(requiredFeature)) {
247
291
  throw new errors.SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
248
292
  code: 'HOST_HANDSHAKE_FAILED',
@@ -250,6 +294,9 @@ class HostTransport {
250
294
  });
251
295
  }
252
296
  }
297
+ this.hostFeatures.clear();
298
+ for (const feature of features)
299
+ this.hostFeatures.add(feature);
253
300
  }
254
301
  resolveWatchdogTimeout(timeoutMsOverride) {
255
302
  return resolveJsWatchdogTimeout(this.watchdogTimeoutMs, this.requestTimeoutMs, timeoutMsOverride);
@@ -349,6 +396,7 @@ class HostTransport {
349
396
  const error = rawError;
350
397
  const data = error.data;
351
398
  const cliCode = typeof data?.cliCode === 'string' ? data.cliCode : undefined;
399
+ const domainCode = typeof data?.domainCode === 'string' ? data.domainCode : undefined;
352
400
  const cliMessage = typeof data?.message === 'string' ? data.message : undefined;
353
401
  const exitCode = typeof data?.exitCode === 'number' ? data.exitCode : undefined;
354
402
  if (cliCode) {
@@ -358,6 +406,16 @@ class HostTransport {
358
406
  exitCode,
359
407
  });
360
408
  }
409
+ if (domainCode) {
410
+ return new errors.SuperDocCliError(error.message, {
411
+ code: domainCode,
412
+ details: {
413
+ ...(data?.details === undefined ? {} : { domainDetails: data.details }),
414
+ ...(data?.stage === undefined ? {} : { stage: data.stage }),
415
+ ...(data?.byteLength === undefined ? {} : { byteLength: data.byteLength }),
416
+ },
417
+ });
418
+ }
361
419
  if (error.code === JSON_RPC_TIMEOUT_CODE) {
362
420
  return new errors.SuperDocCliError(error.message, {
363
421
  code: 'TIMEOUT',
@@ -373,6 +431,8 @@ class HostTransport {
373
431
  this.cleanupProcess(error);
374
432
  }
375
433
  cleanupProcess(error) {
434
+ this.hostFeatures.clear();
435
+ this.documentRpcSessions.clear();
376
436
  const child = this.child;
377
437
  if (child) {
378
438
  child.removeAllListeners();
@@ -398,5 +458,6 @@ class HostTransport {
398
458
  }
399
459
 
400
460
  exports.HostTransport = HostTransport;
461
+ exports.buildDocumentHostSpawnArgs = buildDocumentHostSpawnArgs;
401
462
  exports.buildHostSpawnArgs = buildHostSpawnArgs;
402
463
  exports.resolveJsWatchdogTimeout = resolveJsWatchdogTimeout;
@@ -9,6 +9,7 @@ import { type InvokeOptions, type OperationSpec, type SuperDocClientOptions } fr
9
9
  export declare function buildHostSpawnArgs(prefixArgs: readonly string[], options: {
10
10
  requestTimeoutMs?: number;
11
11
  }): string[];
12
+ export declare function buildDocumentHostSpawnArgs(prefixArgs: readonly string[]): string[];
12
13
  /**
13
14
  * Computes the JS-side watchdog timeout for a single JSON-RPC request.
14
15
  *
@@ -26,11 +27,10 @@ export declare function buildHostSpawnArgs(prefixArgs: readonly string[], option
26
27
  * Exported for unit testing.
27
28
  */
28
29
  export declare function resolveJsWatchdogTimeout(watchdogTimeoutMs: number, requestTimeoutMs: number | undefined, timeoutMsOverride: number | undefined): number;
29
- /**
30
- * Transport that communicates with a long-lived CLI host process over JSON-RPC stdio.
31
- */
30
+ /** Transport for the legacy CLI host or the structured document host. */
32
31
  export declare class HostTransport {
33
- private readonly cliBin;
32
+ private readonly hostBin;
33
+ private readonly processMode;
34
34
  private readonly env?;
35
35
  private readonly startupTimeoutMs;
36
36
  private readonly shutdownTimeoutMs;
@@ -39,14 +39,18 @@ export declare class HostTransport {
39
39
  private readonly maxQueueDepth;
40
40
  private readonly defaultChangeMode?;
41
41
  private readonly user?;
42
+ private readonly documentRpcEnabled;
42
43
  private child;
43
44
  private stdoutReader;
44
45
  private readonly pending;
45
46
  private nextRequestId;
46
47
  private connecting;
47
48
  private stopping;
49
+ private readonly hostFeatures;
50
+ private readonly documentRpcSessions;
48
51
  constructor(options: {
49
- cliBin: string;
52
+ hostBin: string;
53
+ processMode?: 'cli' | 'document';
50
54
  } & SuperDocClientOptions);
51
55
  connect(): Promise<void>;
52
56
  dispose(): Promise<void>;
@@ -2,8 +2,10 @@ 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
6
  const HOST_PROTOCOL_VERSION = '1.0';
6
- const REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
7
+ const CLI_HOST_REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
8
+ const DOCUMENT_HOST_REQUIRED_FEATURES = [...DOCUMENT_RPC_FEATURES, 'host.shutdown'];
7
9
  const CHANGE_MODES = ['direct', 'tracked'];
8
10
  const FORWARD_HOST_STDERR = typeof process !== 'undefined' && typeof process.env?.SUPERDOC_DEBUG_TEXT_REWRITE === 'string'
9
11
  ? process.env.SUPERDOC_DEBUG_TEXT_REWRITE === '1'
@@ -32,6 +34,9 @@ export function buildHostSpawnArgs(prefixArgs, options) {
32
34
  }
33
35
  return args;
34
36
  }
37
+ export function buildDocumentHostSpawnArgs(prefixArgs) {
38
+ return [...prefixArgs];
39
+ }
35
40
  /**
36
41
  * Computes the JS-side watchdog timeout for a single JSON-RPC request.
37
42
  *
@@ -57,11 +62,10 @@ export function resolveJsWatchdogTimeout(watchdogTimeoutMs, requestTimeoutMs, ti
57
62
  }
58
63
  return Math.max(watchdogTimeoutMs, HOST_DEFAULT_REQUEST_TIMEOUT_MS + WATCHDOG_HEADROOM_MS);
59
64
  }
60
- /**
61
- * Transport that communicates with a long-lived CLI host process over JSON-RPC stdio.
62
- */
65
+ /** Transport for the legacy CLI host or the structured document host. */
63
66
  export class HostTransport {
64
- cliBin;
67
+ hostBin;
68
+ processMode;
65
69
  env;
66
70
  startupTimeoutMs;
67
71
  shutdownTimeoutMs;
@@ -70,14 +74,18 @@ export class HostTransport {
70
74
  maxQueueDepth;
71
75
  defaultChangeMode;
72
76
  user;
77
+ documentRpcEnabled;
73
78
  child = null;
74
79
  stdoutReader = null;
75
80
  pending = new Map();
76
81
  nextRequestId = 1;
77
82
  connecting = null;
78
83
  stopping = false;
84
+ hostFeatures = new Set();
85
+ documentRpcSessions = new Set();
79
86
  constructor(options) {
80
- this.cliBin = options.cliBin;
87
+ this.hostBin = options.hostBin;
88
+ this.processMode = options.processMode ?? 'cli';
81
89
  this.env = options.env;
82
90
  this.startupTimeoutMs = options.startupTimeoutMs ?? 5_000;
83
91
  this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 5_000;
@@ -92,6 +100,12 @@ export class HostTransport {
92
100
  }
93
101
  this.defaultChangeMode = options.defaultChangeMode;
94
102
  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
+ }
95
109
  }
96
110
  async connect() {
97
111
  await this.ensureConnected();
@@ -122,6 +136,33 @@ export class HostTransport {
122
136
  }
123
137
  async invoke(operation, params = {}, options = {}) {
124
138
  await this.ensureConnected();
139
+ if (this.documentRpcEnabled &&
140
+ supportsDocumentRpc(this.hostFeatures) &&
141
+ this.defaultChangeMode === undefined &&
142
+ this.user === undefined &&
143
+ 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));
147
+ const result = mapDocumentOpenResult(response, opened.sessionId, params.doc);
148
+ this.documentRpcSessions.add(opened.sessionId);
149
+ return result;
150
+ }
151
+ const sessionId = typeof params.sessionId === 'string' ? params.sessionId : undefined;
152
+ 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);
156
+ if (operation.operationId === 'doc.close')
157
+ this.documentRpcSessions.delete(sessionId);
158
+ return result;
159
+ }
160
+ if (this.processMode === 'document') {
161
+ throw new SuperDocCliError(`Standalone document host does not support ${operation.operationId} through structured RPC v0.`, {
162
+ code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
163
+ details: { operationId: operation.operationId },
164
+ });
165
+ }
125
166
  const argv = buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
126
167
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
127
168
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
@@ -139,13 +180,13 @@ export class HostTransport {
139
180
  return resultRecord.data;
140
181
  }
141
182
  async ensureConnected() {
142
- if (this.child && !this.child.killed) {
143
- return;
144
- }
145
183
  if (this.connecting) {
146
184
  await this.connecting;
147
185
  return;
148
186
  }
187
+ if (this.child && !this.child.killed) {
188
+ return;
189
+ }
149
190
  this.connecting = this.startHostProcess();
150
191
  try {
151
192
  await this.connecting;
@@ -155,8 +196,10 @@ export class HostTransport {
155
196
  }
156
197
  }
157
198
  async startHostProcess() {
158
- const { command, prefixArgs } = resolveInvocation(this.cliBin);
159
- const args = buildHostSpawnArgs(prefixArgs, { requestTimeoutMs: this.requestTimeoutMs });
199
+ const { command, prefixArgs } = resolveInvocation(this.hostBin);
200
+ const args = this.processMode === 'document'
201
+ ? buildDocumentHostSpawnArgs(prefixArgs)
202
+ : buildHostSpawnArgs(prefixArgs, { requestTimeoutMs: this.requestTimeoutMs });
160
203
  const child = spawn(command, args, {
161
204
  env: {
162
205
  ...process.env,
@@ -239,7 +282,8 @@ export class HostTransport {
239
282
  details: { features },
240
283
  });
241
284
  }
242
- for (const requiredFeature of REQUIRED_FEATURES) {
285
+ const requiredFeatures = this.processMode === 'document' ? DOCUMENT_HOST_REQUIRED_FEATURES : CLI_HOST_REQUIRED_FEATURES;
286
+ for (const requiredFeature of requiredFeatures) {
243
287
  if (!features.includes(requiredFeature)) {
244
288
  throw new SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
245
289
  code: 'HOST_HANDSHAKE_FAILED',
@@ -247,6 +291,9 @@ export class HostTransport {
247
291
  });
248
292
  }
249
293
  }
294
+ this.hostFeatures.clear();
295
+ for (const feature of features)
296
+ this.hostFeatures.add(feature);
250
297
  }
251
298
  resolveWatchdogTimeout(timeoutMsOverride) {
252
299
  return resolveJsWatchdogTimeout(this.watchdogTimeoutMs, this.requestTimeoutMs, timeoutMsOverride);
@@ -346,6 +393,7 @@ export class HostTransport {
346
393
  const error = rawError;
347
394
  const data = error.data;
348
395
  const cliCode = typeof data?.cliCode === 'string' ? data.cliCode : undefined;
396
+ const domainCode = typeof data?.domainCode === 'string' ? data.domainCode : undefined;
349
397
  const cliMessage = typeof data?.message === 'string' ? data.message : undefined;
350
398
  const exitCode = typeof data?.exitCode === 'number' ? data.exitCode : undefined;
351
399
  if (cliCode) {
@@ -355,6 +403,16 @@ export class HostTransport {
355
403
  exitCode,
356
404
  });
357
405
  }
406
+ if (domainCode) {
407
+ return new SuperDocCliError(error.message, {
408
+ code: domainCode,
409
+ details: {
410
+ ...(data?.details === undefined ? {} : { domainDetails: data.details }),
411
+ ...(data?.stage === undefined ? {} : { stage: data.stage }),
412
+ ...(data?.byteLength === undefined ? {} : { byteLength: data.byteLength }),
413
+ },
414
+ });
415
+ }
358
416
  if (error.code === JSON_RPC_TIMEOUT_CODE) {
359
417
  return new SuperDocCliError(error.message, {
360
418
  code: 'TIMEOUT',
@@ -370,6 +428,8 @@ export class HostTransport {
370
428
  this.cleanupProcess(error);
371
429
  }
372
430
  cleanupProcess(error) {
431
+ this.hostFeatures.clear();
432
+ this.documentRpcSessions.clear();
373
433
  const child = this.child;
374
434
  if (child) {
375
435
  child.removeAllListeners();
@@ -3,20 +3,28 @@
3
3
  var host = require('./host.cjs');
4
4
  var embeddedCli = require('./embedded-cli.cjs');
5
5
  var debugTrace = require('./debug-trace.cjs');
6
+ var errors = require('./errors.cjs');
6
7
 
7
8
  /**
8
- * Internal runtime that delegates CLI invocations to a persistent host transport.
9
+ * Internal runtime that delegates operations to a persistent host transport.
9
10
  *
10
- * Resolves the CLI binary and creates a {@link HostTransport} that communicates
11
- * with a long-lived `superdoc host --stdio` process.
11
+ * Uses the standalone document host when explicitly configured. Otherwise it
12
+ * preserves the embedded CLI compatibility path.
12
13
  */
13
14
  class SuperDocRuntime {
14
15
  transport;
15
16
  constructor(options = {}) {
16
- const cliBin = options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? embeddedCli.resolveEmbeddedCliBinary();
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();
17
24
  this.transport = new host.HostTransport({
18
- cliBin,
19
25
  ...options,
26
+ hostBin,
27
+ processMode: documentHostBin === undefined ? 'cli' : 'document',
20
28
  });
21
29
  }
22
30
  async connect() {
@@ -1,9 +1,9 @@
1
1
  import type { DocumentRuntimeKind, InvokeOptions, OperationParamSpec, OperationSpec, RuntimeInvoker, SuperDocClientOptions } from './transport-common.js';
2
2
  /**
3
- * Internal runtime that delegates CLI invocations to a persistent host transport.
3
+ * Internal runtime that delegates operations to a persistent host transport.
4
4
  *
5
- * Resolves the CLI binary and creates a {@link HostTransport} that communicates
6
- * with a long-lived `superdoc host --stdio` process.
5
+ * Uses the standalone document host when explicitly configured. Otherwise it
6
+ * preserves the embedded CLI compatibility path.
7
7
  */
8
8
  export declare class SuperDocRuntime {
9
9
  private readonly transport;
@@ -1,19 +1,27 @@
1
1
  import { HostTransport } from './host.js';
2
2
  import { resolveEmbeddedCliBinary } from './embedded-cli.js';
3
3
  import { writeSdkDebugTrace } from './debug-trace.js';
4
+ import { SuperDocCliError } from './errors.js';
4
5
  /**
5
- * Internal runtime that delegates CLI invocations to a persistent host transport.
6
+ * Internal runtime that delegates operations to a persistent host transport.
6
7
  *
7
- * Resolves the CLI binary and creates a {@link HostTransport} that communicates
8
- * with a long-lived `superdoc host --stdio` process.
8
+ * Uses the standalone document host when explicitly configured. Otherwise it
9
+ * preserves the embedded CLI compatibility path.
9
10
  */
10
11
  export class SuperDocRuntime {
11
12
  transport;
12
13
  constructor(options = {}) {
13
- const cliBin = options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? resolveEmbeddedCliBinary();
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();
14
21
  this.transport = new HostTransport({
15
- cliBin,
16
22
  ...options,
23
+ hostBin,
24
+ processMode: documentHostBin === undefined ? 'cli' : 'document',
17
25
  });
18
26
  }
19
27
  async connect() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.4.1",
3
+ "version": "2.6.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,11 +37,11 @@
37
37
  "typescript": "^5.9.2"
38
38
  },
39
39
  "optionalDependencies": {
40
- "@superdoc/sdk-darwin-arm64": "2.4.1",
41
- "@superdoc/sdk-darwin-x64": "2.4.1",
42
- "@superdoc/sdk-linux-x64": "2.4.1",
43
- "@superdoc/sdk-linux-arm64": "2.4.1",
44
- "@superdoc/sdk-windows-x64": "2.4.1"
40
+ "@superdoc/sdk-darwin-arm64": "2.6.0",
41
+ "@superdoc/sdk-linux-x64": "2.6.0",
42
+ "@superdoc/sdk-darwin-x64": "2.6.0",
43
+ "@superdoc/sdk-linux-arm64": "2.6.0",
44
+ "@superdoc/sdk-windows-x64": "2.6.0"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
@@ -50,6 +50,7 @@
50
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",
51
51
  "audit:publish": "node ../../../../scripts/audit-publish-artifact.mjs dist --label sdk-node-dist",
52
52
  "typecheck": "tsc --noEmit",
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",
53
54
  "smoke:product-action": "node scripts/product-action-smoke.mjs"
54
55
  }
55
56
  }
@@ -5,7 +5,7 @@
5
5
  "tools": [
6
6
  {
7
7
  "toolName": "superdoc_get_content",
8
- "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", and filtering via \"nodeTypes\". Other actions ignore these parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
8
+ "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. Its optional reviewMode selects final, original, or redline numbering metadata without filtering blocks. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", filtering via \"nodeTypes\", and numbering projection via \"reviewMode\". Other actions ignore pagination and filtering parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20,\"reviewMode\":\"final\"}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
9
9
  "inputSchema": {
10
10
  "type": "object",
11
11
  "properties": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "reviewMode": {
35
35
  "$ref": "#/$defs/SDProjectionReviewMode",
36
- "description": "Only for actions 'markdown_projection', 'html_projection'. Omit for other actions."
36
+ "description": "Only for actions 'markdown_projection', 'html_projection', 'blocks'. Omit for other actions."
37
37
  },
38
38
  "scope": {
39
39
  "$ref": "#/$defs/SDProjectionScope",
@@ -43,5 +43,5 @@
43
43
  "mutates": true
44
44
  }
45
45
  ],
46
- "contractHash": "bfefe3880487f715579f3ba1bf11d94dd1af4151131b780a7d26dae65ca8c38e"
46
+ "contractHash": "5967b3cb6c6ca5f65dd887d1c3b2908478e47ac6a2b76f7b2bb207f3b5b21324"
47
47
  }
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "superdoc_get_content",
6
- "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", and filtering via \"nodeTypes\". Other actions ignore these parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
6
+ "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. Its optional reviewMode selects final, original, or redline numbering metadata without filtering blocks. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", filtering via \"nodeTypes\", and numbering projection via \"reviewMode\". Other actions ignore pagination and filtering parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20,\"reviewMode\":\"final\"}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
7
7
  "input_schema": {
8
8
  "type": "object",
9
9
  "properties": {
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "reviewMode": {
33
33
  "$ref": "#/$defs/SDProjectionReviewMode",
34
- "description": "Only for actions 'markdown_projection', 'html_projection'. Omit for other actions."
34
+ "description": "Only for actions 'markdown_projection', 'html_projection', 'blocks'. Omit for other actions."
35
35
  },
36
36
  "scope": {
37
37
  "$ref": "#/$defs/SDProjectionScope",
@@ -3,7 +3,7 @@
3
3
  "tools": [
4
4
  {
5
5
  "name": "superdoc_get_content",
6
- "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", and filtering via \"nodeTypes\". Other actions ignore these parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
6
+ "description": "Read document content in various formats. Call this first in any workflow to understand document structure before making edits. Action \"blocks\" returns structured block data with nodeId, nodeType, textPreview, optional full text when includeText:true, formatting properties (fontFamily, fontSize, color, bold, underline, alignment), and ref handles for immediate use with superdoc_edit or superdoc_format. Its optional reviewMode selects final, original, or redline numbering metadata without filtering blocks. When you need to evaluate or rewrite existing paragraphs or clauses, prefer action \"blocks\" with includeText:true so you can identify the correct block and then target it by nodeId. Actions \"text\", \"markdown\", and \"html\" return compact strings. Actions \"markdown_projection\" and \"html_projection\" return detailed async projections with review modes, scopes, diagnostics, annotations, block maps, and optional source maps. Projection source targets use tracked coordinates and are valid only at evaluatedRevision; after any mutation, reproject before using them. Treat omitted or partially emitted annotations according to their status instead of guessing an anchor from rendered text. Action \"info\" returns document metadata: word count, paragraph count, page count, outline, available styles, and capability flags. The \"blocks\" action supports pagination via \"offset\" and \"limit\", filtering via \"nodeTypes\", and numbering projection via \"reviewMode\". Other actions ignore pagination and filtering parameters. This tool never modifies the document. Do NOT call superdoc_edit or superdoc_format without first reading blocks to get valid refs and formatting reference values.\n\nEXAMPLES:\n 1. {\"action\":\"blocks\"}\n 2. {\"action\":\"blocks\",\"includeText\":true,\"offset\":0,\"limit\":20,\"reviewMode\":\"final\"}\n 3. {\"action\":\"blocks\",\"offset\":0,\"limit\":20,\"nodeTypes\":[\"heading\",\"paragraph\"]}\n 4. {\"action\":\"text\"}\n 5. {\"action\":\"info\"}\n 6. {\"action\":\"html_projection\",\"reviewMode\":\"redline\",\"includeSourceMap\":true}\n 7. {\"action\":\"markdown_projection\",\"reviewMode\":\"original\",\"scope\":{\"kind\":\"block\",\"nodeType\":\"paragraph\",\"nodeId\":\"paragraph-id\"},\"includeSourceMap\":true}",
7
7
  "parameters": {
8
8
  "type": "object",
9
9
  "properties": {
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "reviewMode": {
33
33
  "$ref": "#/$defs/SDProjectionReviewMode",
34
- "description": "Only for actions 'markdown_projection', 'html_projection'. Omit for other actions."
34
+ "description": "Only for actions 'markdown_projection', 'html_projection', 'blocks'. Omit for other actions."
35
35
  },
36
36
  "scope": {
37
37
  "$ref": "#/$defs/SDProjectionScope",