@superdoc/sdk 2.10.0-next.4 → 2.10.0-next.6

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.
@@ -1,7 +1,7 @@
1
1
  import { CONTRACT } from '../generated/contract.js';
2
2
  import { SuperDocCliError } from '../runtime/errors.js';
3
3
  import { validatePlan } from './ir.js';
4
- import { buildDocumentSnapshot, resolveSnapshotSelector, AmbiguousSelectorError, } from './doc-snapshot.js';
4
+ import { buildDocumentSnapshot, buildMutationSnapshot, resolveSnapshotSelector, AmbiguousSelectorError, MutationSnapshotError, } from './doc-snapshot.js';
5
5
  import { getOperationCatalogEntry } from './operation-catalog.js';
6
6
  const RESERVED_ARG_KEYS = new Set(['sessionId', 'doc']);
7
7
  function ensureKnownOperation(operationId) {
@@ -224,8 +224,8 @@ function computeDeltaChecks(pre, post, checks, saveReopen) {
224
224
  else if (check.kind === 'comment-count-delta') {
225
225
  results.push({
226
226
  check,
227
- passed: post.comments.length - pre.comments.length === check.delta,
228
- detail: `pre=${pre.comments.length} post=${post.comments.length}`,
227
+ passed: post.counts.comments - pre.counts.comments === check.delta,
228
+ detail: `pre=${pre.counts.comments} post=${post.counts.comments}`,
229
229
  });
230
230
  }
231
231
  else if (check.kind === 'tracked-change-count-delta') {
@@ -310,7 +310,7 @@ async function trySaveReopen(doc, checks) {
310
310
  await saveAny.call(doc, {});
311
311
  // Rebuild a fresh snapshot after save. Host-level true reopen still needs
312
312
  // a new document handle, which this runtime cannot force on its own.
313
- const fresh = await buildDocumentSnapshot(doc);
313
+ const fresh = await buildMutationSnapshot(doc);
314
314
  for (const check of checks) {
315
315
  if (check.kind === 'save-reopen-text-contains') {
316
316
  const found = fresh.blocks.some((b) => b.text.includes(check.text));
@@ -347,7 +347,23 @@ export async function agentApply(doc, args) {
347
347
  errors: validation.errors.map((e) => ({ code: e.code, message: e.message })),
348
348
  };
349
349
  }
350
- const preSnapshot = await buildDocumentSnapshot(doc);
350
+ let preSnapshot;
351
+ try {
352
+ preSnapshot = await buildMutationSnapshot(doc);
353
+ }
354
+ catch (error) {
355
+ if (!(error instanceof MutationSnapshotError))
356
+ throw error;
357
+ return {
358
+ status: 'failed',
359
+ intent: plan.intent,
360
+ preSnapshot: { revision: 'unknown', counts: emptyCounts() },
361
+ selectedTargets: [],
362
+ executedOperations: [],
363
+ verification: [],
364
+ errors: [{ code: error.code, message: error.message, recovery: { kind: 'retry' } }],
365
+ };
366
+ }
351
367
  const selectedTargets = [];
352
368
  const executedOperations = [];
353
369
  const bindings = new Map();
@@ -412,7 +428,23 @@ export async function agentApply(doc, args) {
412
428
  errors: [{ code: 'APPLY_FAILED', message }],
413
429
  };
414
430
  }
415
- const postSnapshot = await buildDocumentSnapshot(doc);
431
+ let postSnapshot;
432
+ try {
433
+ postSnapshot = await buildMutationSnapshot(doc);
434
+ }
435
+ catch (error) {
436
+ if (!(error instanceof MutationSnapshotError))
437
+ throw error;
438
+ return {
439
+ status: 'failed',
440
+ intent: plan.intent,
441
+ preSnapshot: { revision: preSnapshot.revision, counts: preSnapshot.counts },
442
+ selectedTargets,
443
+ executedOperations,
444
+ verification: [],
445
+ errors: [{ code: error.code, message: error.message, recovery: { kind: 'reinspect' } }],
446
+ };
447
+ }
416
448
  const verifyStep = plan.steps.find((s) => s.kind === 'verify');
417
449
  let saveReopen;
418
450
  const shouldSaveReopen = (verifyStep?.kind === 'verify' && (verifyStep.saveReopen || verificationNeedsSaveReopen(verifyStep.checks))) ||
@@ -434,7 +466,23 @@ export async function agentApply(doc, args) {
434
466
  };
435
467
  }
436
468
  export async function agentVerify(doc, args) {
437
- const snapshot = await buildDocumentSnapshot(doc);
469
+ let snapshot;
470
+ try {
471
+ snapshot = await buildMutationSnapshot(doc);
472
+ }
473
+ catch (error) {
474
+ if (!(error instanceof MutationSnapshotError))
475
+ throw error;
476
+ return {
477
+ status: 'failed',
478
+ intent: 'verify',
479
+ preSnapshot: { revision: 'unknown', counts: emptyCounts() },
480
+ selectedTargets: [],
481
+ executedOperations: [],
482
+ verification: [],
483
+ errors: [{ code: error.code, message: error.message, recovery: { kind: 'retry' } }],
484
+ };
485
+ }
438
486
  let saveReopen;
439
487
  if (args.saveReopen || verificationNeedsSaveReopen(args.checks)) {
440
488
  saveReopen = await trySaveReopen(doc, args.checks);
@@ -21,6 +21,7 @@ const CONTRACT = {
21
21
  "document.open",
22
22
  "document.open.defaultChangeMode",
23
23
  "document.currentRevision",
24
+ "document.replaceFile",
24
25
  "document.invoke",
25
26
  "document.save",
26
27
  "document.save.source",
@@ -2905,6 +2905,7 @@ export const CONTRACT = {
2905
2905
  "document.open",
2906
2906
  "document.open.defaultChangeMode",
2907
2907
  "document.currentRevision",
2908
+ "document.replaceFile",
2908
2909
  "document.invoke",
2909
2910
  "document.save",
2910
2911
  "document.save.source",
@@ -2936,6 +2937,7 @@ export const CONTRACT = {
2936
2937
  "document.open",
2937
2938
  "document.open.defaultChangeMode",
2938
2939
  "document.currentRevision",
2940
+ "document.replaceFile",
2939
2941
  "document.invoke",
2940
2942
  "document.save",
2941
2943
  "document.save.source",
package/dist/index.cjs CHANGED
@@ -37,6 +37,15 @@ class BoundRuntime {
37
37
  }
38
38
  return this.runtime.invoke(operation, { ...params, sessionId: this.sessionId }, options);
39
39
  }
40
+ async replaceFile(source, options = {}) {
41
+ if (this.closed) {
42
+ throw new errors.SuperDocCliError('Document handle is closed; cannot replace its file.', {
43
+ code: 'DOCUMENT_CLOSED',
44
+ details: { sessionId: this.sessionId },
45
+ });
46
+ }
47
+ return this.runtime.replaceFile(this.sessionId, source, options);
48
+ }
40
49
  markClosed() {
41
50
  this.closed = true;
42
51
  }
@@ -72,6 +81,9 @@ class SuperDocDocumentCore {
72
81
  async save(params = {}, options = {}) {
73
82
  return this.boundRuntime.invoke(contract.CONTRACT.operations['doc.save'], params, options);
74
83
  }
84
+ async replaceFile(source, options = {}) {
85
+ return this.boundRuntime.replaceFile(source, options);
86
+ }
75
87
  async close(params = {}, options = {}) {
76
88
  const result = await this.boundRuntime.invoke(contract.CONTRACT.operations['doc.close'], params, options);
77
89
  this.boundRuntime.markClosed();
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type BoundDocApi, type DocCloseBoundParams, type DocCloseResult, type DocFormatApplyBoundParams, type DocFormatApplyResult, type DocOpenParams as GeneratedDocOpenParams, type DocOpenResult, type DocSaveBoundParams, type DocSaveResult } from './generated/client.js';
2
- import { SuperDocRuntime, type SuperDocClientOptions, type DocOpenOptions, type InvokeOptions, type OperationSpec, type RuntimeInvoker } from './runtime/process.js';
2
+ import { SuperDocRuntime, type SuperDocClientOptions, type DocOpenOptions, type InvokeOptions, type OperationSpec, type ReplaceFileOptions, type ReplaceFileResult, type ReplaceFileSource, type RuntimeInvoker } from './runtime/process.js';
3
3
  /**
4
4
  * Wraps a raw runtime and injects a fixed sessionId into every invoke call.
5
5
  * Implements RuntimeInvoker so generated code can use it directly.
@@ -12,6 +12,7 @@ declare class BoundRuntime implements RuntimeInvoker {
12
12
  private closed;
13
13
  constructor(runtime: SuperDocRuntime, sessionId: string);
14
14
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: InvokeOptions): Promise<TData>;
15
+ replaceFile(source: ReplaceFileSource, options?: ReplaceFileOptions): Promise<ReplaceFileResult>;
15
16
  markClosed(): void;
16
17
  }
17
18
  export interface DocFormatRangeBoundParams extends Omit<DocFormatApplyBoundParams, 'inline'> {
@@ -33,6 +34,7 @@ declare class SuperDocDocumentCore {
33
34
  /** Read-only snapshot of the initial doc.open response metadata. */
34
35
  get openResult(): DocOpenResult;
35
36
  save(params?: DocSaveBoundParams, options?: InvokeOptions): Promise<DocSaveResult>;
37
+ replaceFile(source: ReplaceFileSource, options?: ReplaceFileOptions): Promise<ReplaceFileResult>;
36
38
  close(params?: DocCloseBoundParams, options?: InvokeOptions): Promise<DocCloseResult>;
37
39
  /** @internal */
38
40
  markClosed(): void;
@@ -42,6 +44,7 @@ type SuperDocDocumentInstance = SuperDocDocumentCore & BoundDocApi;
42
44
  export declare const SuperDocDocument: new (boundRuntime: BoundRuntime, sessionId: string, openResult: DocOpenResult, client: SuperDocClient) => SuperDocDocumentInstance;
43
45
  export type SuperDocDocument = SuperDocDocumentInstance;
44
46
  export type DocOpenParams = GeneratedDocOpenParams;
47
+ export type { ReplaceFileOptions, ReplaceFileResult, ReplaceFileSource };
45
48
  export interface DocDescribeCommandParams {
46
49
  operationId: string;
47
50
  [key: string]: unknown;
package/dist/index.js CHANGED
@@ -29,6 +29,15 @@ class BoundRuntime {
29
29
  }
30
30
  return this.runtime.invoke(operation, { ...params, sessionId: this.sessionId }, options);
31
31
  }
32
+ async replaceFile(source, options = {}) {
33
+ if (this.closed) {
34
+ throw new SuperDocCliError('Document handle is closed; cannot replace its file.', {
35
+ code: 'DOCUMENT_CLOSED',
36
+ details: { sessionId: this.sessionId },
37
+ });
38
+ }
39
+ return this.runtime.replaceFile(this.sessionId, source, options);
40
+ }
32
41
  markClosed() {
33
42
  this.closed = true;
34
43
  }
@@ -64,6 +73,9 @@ class SuperDocDocumentCore {
64
73
  async save(params = {}, options = {}) {
65
74
  return this.boundRuntime.invoke(CONTRACT.operations['doc.save'], params, options);
66
75
  }
76
+ async replaceFile(source, options = {}) {
77
+ return this.boundRuntime.replaceFile(source, options);
78
+ }
67
79
  async close(params = {}, options = {}) {
68
80
  const result = await this.boundRuntime.invoke(CONTRACT.operations['doc.close'], params, options);
69
81
  this.boundRuntime.markClosed();
@@ -5,6 +5,7 @@ var errors = require('./errors.cjs');
5
5
  var transportCommon = require('./transport-common.cjs');
6
6
 
7
7
  const DOCUMENT_RPC_FEATURES = ['document.open', 'document.invoke', 'document.save', 'document.close'];
8
+ const DOCUMENT_RPC_REPLACE_FILE_FEATURE = 'document.replaceFile';
8
9
  const DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE = 'document.open.defaultChangeMode';
9
10
  const DOCUMENT_RPC_SOURCE_SAVE_FEATURE = 'document.save.source';
10
11
  const DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE = 'host.request.timeoutMs';
@@ -103,6 +104,30 @@ function mapDocumentOpenResult(response, sessionId, path) {
103
104
  dirty: false,
104
105
  };
105
106
  }
107
+ function mapDocumentReplaceFileResult(response, sessionId) {
108
+ if (typeof response !== 'object' || response == null || Array.isArray(response)) {
109
+ throw new errors.SuperDocCliError('Host returned invalid document.replaceFile result.', {
110
+ code: 'HOST_PROTOCOL_ERROR',
111
+ details: { result: response },
112
+ });
113
+ }
114
+ const record = response;
115
+ if (record.sessionId !== sessionId ||
116
+ record.replaced !== true ||
117
+ typeof record.byteLength !== 'number' ||
118
+ typeof record.revision !== 'string') {
119
+ throw new errors.SuperDocCliError('Host returned invalid document.replaceFile session metadata.', {
120
+ code: 'HOST_PROTOCOL_ERROR',
121
+ details: { expectedSessionId: sessionId, result: response },
122
+ });
123
+ }
124
+ return {
125
+ contextId: sessionId,
126
+ runtime: 'v2',
127
+ replaced: true,
128
+ document: { byteLength: record.byteLength, revision: record.revision },
129
+ };
130
+ }
106
131
  function supportsTrackedMode(operation) {
107
132
  return operation.supportsTrackedMode === true || operation.supportsConditionalTrackedMode === true;
108
133
  }
@@ -316,6 +341,7 @@ function mapDocumentLifecycleResult(operationId, response, sessionId, expectedIn
316
341
 
317
342
  exports.DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE = DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE;
318
343
  exports.DOCUMENT_RPC_FEATURES = DOCUMENT_RPC_FEATURES;
344
+ exports.DOCUMENT_RPC_REPLACE_FILE_FEATURE = DOCUMENT_RPC_REPLACE_FILE_FEATURE;
319
345
  exports.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE = DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE;
320
346
  exports.DOCUMENT_RPC_SOURCE_SAVE_FEATURE = DOCUMENT_RPC_SOURCE_SAVE_FEATURE;
321
347
  exports.buildDocumentInvokeParams = buildDocumentInvokeParams;
@@ -325,4 +351,5 @@ exports.documentRpcEnabled = documentRpcEnabled;
325
351
  exports.documentRpcRequestSupportsResponseTimeout = documentRpcRequestSupportsResponseTimeout;
326
352
  exports.mapDocumentLifecycleResult = mapDocumentLifecycleResult;
327
353
  exports.mapDocumentOpenResult = mapDocumentOpenResult;
354
+ exports.mapDocumentReplaceFileResult = mapDocumentReplaceFileResult;
328
355
  exports.supportsDocumentRpc = supportsDocumentRpc;
@@ -1,5 +1,6 @@
1
1
  import { type ChangeMode, type InvokeOptions, type OperationSpec, type UserIdentity } from './transport-common.js';
2
2
  export declare const DOCUMENT_RPC_FEATURES: readonly ["document.open", "document.invoke", "document.save", "document.close"];
3
+ export declare const DOCUMENT_RPC_REPLACE_FILE_FEATURE = "document.replaceFile";
3
4
  export declare const DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE = "document.open.defaultChangeMode";
4
5
  export declare const DOCUMENT_RPC_SOURCE_SAVE_FEATURE = "document.save.source";
5
6
  export declare const DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE = "host.request.timeoutMs";
@@ -15,6 +16,7 @@ export declare function buildDocumentOpenParams(params: Record<string, unknown>,
15
16
  readonly params: Record<string, unknown>;
16
17
  };
17
18
  export declare function mapDocumentOpenResult(response: unknown, sessionId: string, path: string): Record<string, unknown>;
19
+ export declare function mapDocumentReplaceFileResult(response: unknown, sessionId: string): Record<string, unknown>;
18
20
  export declare function buildDocumentInvokeParams(sessionId: string, operation: OperationSpec, params: Record<string, unknown>, options: InvokeOptions, hostFeatures?: ReadonlySet<string>): {
19
21
  readonly method: string;
20
22
  readonly params: Record<string, unknown>;
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { SuperDocCliError } from './errors.js';
3
3
  import { applyOperationParamAliases, } from './transport-common.js';
4
4
  export const DOCUMENT_RPC_FEATURES = ['document.open', 'document.invoke', 'document.save', 'document.close'];
5
+ export const DOCUMENT_RPC_REPLACE_FILE_FEATURE = 'document.replaceFile';
5
6
  export const DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE = 'document.open.defaultChangeMode';
6
7
  export const DOCUMENT_RPC_SOURCE_SAVE_FEATURE = 'document.save.source';
7
8
  export const DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE = 'host.request.timeoutMs';
@@ -100,6 +101,30 @@ export function mapDocumentOpenResult(response, sessionId, path) {
100
101
  dirty: false,
101
102
  };
102
103
  }
104
+ export function mapDocumentReplaceFileResult(response, sessionId) {
105
+ if (typeof response !== 'object' || response == null || Array.isArray(response)) {
106
+ throw new SuperDocCliError('Host returned invalid document.replaceFile result.', {
107
+ code: 'HOST_PROTOCOL_ERROR',
108
+ details: { result: response },
109
+ });
110
+ }
111
+ const record = response;
112
+ if (record.sessionId !== sessionId ||
113
+ record.replaced !== true ||
114
+ typeof record.byteLength !== 'number' ||
115
+ typeof record.revision !== 'string') {
116
+ throw new SuperDocCliError('Host returned invalid document.replaceFile session metadata.', {
117
+ code: 'HOST_PROTOCOL_ERROR',
118
+ details: { expectedSessionId: sessionId, result: response },
119
+ });
120
+ }
121
+ return {
122
+ contextId: sessionId,
123
+ runtime: 'v2',
124
+ replaced: true,
125
+ document: { byteLength: record.byteLength, revision: record.revision },
126
+ };
127
+ }
103
128
  function supportsTrackedMode(operation) {
104
129
  return operation.supportsTrackedMode === true || operation.supportsConditionalTrackedMode === true;
105
130
  }
@@ -187,6 +187,34 @@ class HostTransport {
187
187
  async invoke(operation, params = {}, options = {}) {
188
188
  return this.runWhileActive(() => this.invokeWhileActive(operation, params, options));
189
189
  }
190
+ async replaceFile(sessionId, path, options = {}) {
191
+ return this.runWhileActive(async () => {
192
+ await this.ensureConnected();
193
+ if (!this.hostFeatures.has(documentRpc.DOCUMENT_RPC_REPLACE_FILE_FEATURE)) {
194
+ throw new errors.SuperDocCliError('The connected host does not support document replacement.', {
195
+ code: 'CAPABILITY_UNSUPPORTED',
196
+ details: { feature: documentRpc.DOCUMENT_RPC_REPLACE_FILE_FEATURE },
197
+ });
198
+ }
199
+ const tracksDirectSession = this.documentRpcSessions.has(sessionId);
200
+ if (tracksDirectSession)
201
+ this.beginDocumentRpcMutation(sessionId);
202
+ try {
203
+ const response = await this.sendJsonRpcRequest(documentRpc.DOCUMENT_RPC_REPLACE_FILE_FEATURE, { sessionId, path }, this.resolveWatchdogTimeout(options.timeoutMs));
204
+ const result = documentRpc.mapDocumentReplaceFileResult(response, sessionId);
205
+ if (tracksDirectSession)
206
+ this.settleDocumentRpcMutation(sessionId, 'applied');
207
+ return result;
208
+ }
209
+ catch (error) {
210
+ if (tracksDirectSession) {
211
+ const indeterminate = error instanceof errors.SuperDocCliError && ['TIMEOUT', 'HOST_TIMEOUT', 'HOST_DISCONNECTED'].includes(error.code);
212
+ this.settleDocumentRpcMutation(sessionId, indeterminate ? 'indeterminate' : 'not-applied');
213
+ }
214
+ throw error;
215
+ }
216
+ });
217
+ }
190
218
  async invokeWhileActive(operation, params, options) {
191
219
  const collaborationAuth = options.collaborationAuth === undefined ? undefined : transportCommon.normalizeCollaborationAuth(options.collaborationAuth);
192
220
  if (collaborationAuth !== undefined) {
@@ -285,8 +313,11 @@ class HostTransport {
285
313
  details: { operationId: operation.operationId },
286
314
  });
287
315
  }
316
+ const supportsRequestTimeoutMetadata = this.hostFeatures.has(documentRpc.DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE);
288
317
  const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
289
- if (options.timeoutMs !== undefined && options.timeoutMs > cliHostRequestTimeoutMs) {
318
+ if (!supportsRequestTimeoutMetadata &&
319
+ options.timeoutMs !== undefined &&
320
+ options.timeoutMs > cliHostRequestTimeoutMs) {
290
321
  throw new errors.SuperDocCliError(`CLI host cannot honor timeoutMs=${options.timeoutMs} above its ${cliHostRequestTimeoutMs}ms request ceiling.`, {
291
322
  code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
292
323
  details: {
@@ -304,7 +335,9 @@ class HostTransport {
304
335
  argv,
305
336
  stdinBase64,
306
337
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
307
- }, watchdogTimeout);
338
+ }, watchdogTimeout, {
339
+ requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
340
+ });
308
341
  if (typeof response !== 'object' || response == null || Array.isArray(response)) {
309
342
  throw new errors.SuperDocCliError('Host returned invalid cli.invoke result.', {
310
343
  code: 'HOST_PROTOCOL_ERROR',
@@ -64,6 +64,7 @@ export declare class HostTransport {
64
64
  dispose(): Promise<void>;
65
65
  private disposeAfterActiveCalls;
66
66
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: TransportInvokeOptions): Promise<TData>;
67
+ replaceFile(sessionId: string, path: string, options?: TransportInvokeOptions): Promise<Record<string, unknown>>;
67
68
  private invokeWhileActive;
68
69
  private runWhileActive;
69
70
  private ensureConnected;
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline';
3
3
  import { buildOperationArgv, normalizeCollaborationAuth, resolveInvocation, } from './transport-common.js';
4
4
  import { SuperDocCliError } from './errors.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';
5
+ import { DOCUMENT_RPC_FEATURES, DOCUMENT_RPC_DEFAULT_CHANGE_MODE_FEATURE, DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE, DOCUMENT_RPC_REPLACE_FILE_FEATURE, DOCUMENT_RPC_SOURCE_SAVE_FEATURE, buildDocumentInvokeParams, buildDocumentOpenParams, canOpenWithDocumentRpc, documentRpcRequestSupportsResponseTimeout, documentRpcEnabled, mapDocumentLifecycleResult, mapDocumentOpenResult, mapDocumentReplaceFileResult, 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
  export const COLLABORATION_AUTH_PER_OPEN_FEATURE = 'collaboration.auth.perOpen';
@@ -184,6 +184,34 @@ export class HostTransport {
184
184
  async invoke(operation, params = {}, options = {}) {
185
185
  return this.runWhileActive(() => this.invokeWhileActive(operation, params, options));
186
186
  }
187
+ async replaceFile(sessionId, path, options = {}) {
188
+ return this.runWhileActive(async () => {
189
+ await this.ensureConnected();
190
+ if (!this.hostFeatures.has(DOCUMENT_RPC_REPLACE_FILE_FEATURE)) {
191
+ throw new SuperDocCliError('The connected host does not support document replacement.', {
192
+ code: 'CAPABILITY_UNSUPPORTED',
193
+ details: { feature: DOCUMENT_RPC_REPLACE_FILE_FEATURE },
194
+ });
195
+ }
196
+ const tracksDirectSession = this.documentRpcSessions.has(sessionId);
197
+ if (tracksDirectSession)
198
+ this.beginDocumentRpcMutation(sessionId);
199
+ try {
200
+ const response = await this.sendJsonRpcRequest(DOCUMENT_RPC_REPLACE_FILE_FEATURE, { sessionId, path }, this.resolveWatchdogTimeout(options.timeoutMs));
201
+ const result = mapDocumentReplaceFileResult(response, sessionId);
202
+ if (tracksDirectSession)
203
+ this.settleDocumentRpcMutation(sessionId, 'applied');
204
+ return result;
205
+ }
206
+ catch (error) {
207
+ if (tracksDirectSession) {
208
+ const indeterminate = error instanceof SuperDocCliError && ['TIMEOUT', 'HOST_TIMEOUT', 'HOST_DISCONNECTED'].includes(error.code);
209
+ this.settleDocumentRpcMutation(sessionId, indeterminate ? 'indeterminate' : 'not-applied');
210
+ }
211
+ throw error;
212
+ }
213
+ });
214
+ }
187
215
  async invokeWhileActive(operation, params, options) {
188
216
  const collaborationAuth = options.collaborationAuth === undefined ? undefined : normalizeCollaborationAuth(options.collaborationAuth);
189
217
  if (collaborationAuth !== undefined) {
@@ -282,8 +310,11 @@ export class HostTransport {
282
310
  details: { operationId: operation.operationId },
283
311
  });
284
312
  }
313
+ const supportsRequestTimeoutMetadata = this.hostFeatures.has(DOCUMENT_RPC_REQUEST_TIMEOUT_FEATURE);
285
314
  const cliHostRequestTimeoutMs = this.requestTimeoutMs ?? HOST_DEFAULT_REQUEST_TIMEOUT_MS;
286
- if (options.timeoutMs !== undefined && options.timeoutMs > cliHostRequestTimeoutMs) {
315
+ if (!supportsRequestTimeoutMetadata &&
316
+ options.timeoutMs !== undefined &&
317
+ options.timeoutMs > cliHostRequestTimeoutMs) {
287
318
  throw new SuperDocCliError(`CLI host cannot honor timeoutMs=${options.timeoutMs} above its ${cliHostRequestTimeoutMs}ms request ceiling.`, {
288
319
  code: 'DOCUMENT_RPC_INPUT_UNSUPPORTED',
289
320
  details: {
@@ -301,7 +332,9 @@ export class HostTransport {
301
332
  argv,
302
333
  stdinBase64,
303
334
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
304
- }, watchdogTimeout);
335
+ }, watchdogTimeout, {
336
+ requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
337
+ });
305
338
  if (typeof response !== 'object' || response == null || Array.isArray(response)) {
306
339
  throw new SuperDocCliError('Host returned invalid cli.invoke result.', {
307
340
  code: 'HOST_PROTOCOL_ERROR',
@@ -1,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var promises = require('node:fs/promises');
4
+ var os = require('node:os');
5
+ var path = require('node:path');
3
6
  var host = require('./host.cjs');
4
7
  var embeddedCli = require('./embedded-cli.cjs');
5
8
  var embeddedDocumentHost = require('./embedded-document-host.cjs');
@@ -88,6 +91,35 @@ class SuperDocRuntime {
88
91
  throw error;
89
92
  }
90
93
  }
94
+ async replaceFile(sessionId, source, options = {}) {
95
+ if (typeof source === 'string') {
96
+ if (source.trim().length === 0) {
97
+ throw new errors.SuperDocCliError('replaceFile source path must be non-empty.', { code: 'INVALID_ARGUMENT' });
98
+ }
99
+ return (await this.transport.replaceFile(sessionId, source, options));
100
+ }
101
+ let bytes;
102
+ if (source instanceof Uint8Array) {
103
+ bytes = new Uint8Array(source);
104
+ }
105
+ else if (source instanceof ArrayBuffer) {
106
+ bytes = new Uint8Array(source.slice(0));
107
+ }
108
+ else {
109
+ throw new errors.SuperDocCliError('replaceFile source must be a path, Uint8Array, or ArrayBuffer.', {
110
+ code: 'INVALID_ARGUMENT',
111
+ });
112
+ }
113
+ const stagingDirectory = await promises.mkdtemp(path.join(os.tmpdir(), 'superdoc-sdk-replace-'));
114
+ const stagedPath = path.join(stagingDirectory, 'replacement.docx');
115
+ try {
116
+ await promises.writeFile(stagedPath, bytes, { mode: 0o600 });
117
+ return (await this.transport.replaceFile(sessionId, stagedPath, options));
118
+ }
119
+ finally {
120
+ await promises.rm(stagingDirectory, { recursive: true, force: true });
121
+ }
122
+ }
91
123
  }
92
124
 
93
125
  exports.SuperDocRuntime = SuperDocRuntime;
@@ -7,6 +7,19 @@ type EmbeddedRuntimeResolvers = {
7
7
  cli: () => string;
8
8
  documentHost: () => string;
9
9
  };
10
+ export type ReplaceFileSource = string | Uint8Array | ArrayBuffer;
11
+ export interface ReplaceFileOptions {
12
+ readonly timeoutMs?: number;
13
+ }
14
+ export interface ReplaceFileResult {
15
+ readonly contextId: string;
16
+ readonly runtime: 'v2';
17
+ readonly replaced: true;
18
+ readonly document: {
19
+ readonly byteLength: number;
20
+ readonly revision: string;
21
+ };
22
+ }
10
23
  export declare function resolveRuntimeProcess(options?: SuperDocClientOptions, embedded?: EmbeddedRuntimeResolvers): RuntimeProcessResolution;
11
24
  export declare function toSafeInvokeTraceOptions(options: TransportInvokeOptions): InvokeOptions;
12
25
  /**
@@ -21,5 +34,6 @@ export declare class SuperDocRuntime {
21
34
  connect(): Promise<void>;
22
35
  dispose(): Promise<void>;
23
36
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: TransportInvokeOptions): Promise<TData>;
37
+ replaceFile(sessionId: string, source: ReplaceFileSource, options?: ReplaceFileOptions): Promise<ReplaceFileResult>;
24
38
  }
25
39
  export type { CollaborationAuth, DocumentRuntimeKind, DocOpenOptions, InvokeOptions, OperationParamSpec, OperationSpec, RuntimeInvoker, SuperDocClientOptions, };
@@ -1,3 +1,6 @@
1
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
1
4
  import { HostTransport } from './host.js';
2
5
  import { resolveEmbeddedCliBinary } from './embedded-cli.js';
3
6
  import { resolveEmbeddedDocumentHostBinary } from './embedded-document-host.js';
@@ -85,4 +88,33 @@ export class SuperDocRuntime {
85
88
  throw error;
86
89
  }
87
90
  }
91
+ async replaceFile(sessionId, source, options = {}) {
92
+ if (typeof source === 'string') {
93
+ if (source.trim().length === 0) {
94
+ throw new SuperDocCliError('replaceFile source path must be non-empty.', { code: 'INVALID_ARGUMENT' });
95
+ }
96
+ return (await this.transport.replaceFile(sessionId, source, options));
97
+ }
98
+ let bytes;
99
+ if (source instanceof Uint8Array) {
100
+ bytes = new Uint8Array(source);
101
+ }
102
+ else if (source instanceof ArrayBuffer) {
103
+ bytes = new Uint8Array(source.slice(0));
104
+ }
105
+ else {
106
+ throw new SuperDocCliError('replaceFile source must be a path, Uint8Array, or ArrayBuffer.', {
107
+ code: 'INVALID_ARGUMENT',
108
+ });
109
+ }
110
+ const stagingDirectory = await mkdtemp(join(tmpdir(), 'superdoc-sdk-replace-'));
111
+ const stagedPath = join(stagingDirectory, 'replacement.docx');
112
+ try {
113
+ await writeFile(stagedPath, bytes, { mode: 0o600 });
114
+ return (await this.transport.replaceFile(sessionId, stagedPath, options));
115
+ }
116
+ finally {
117
+ await rm(stagingDirectory, { recursive: true, force: true });
118
+ }
119
+ }
88
120
  }
@@ -3,6 +3,6 @@
3
3
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
4
4
  // Source of truth: package.json. Regenerated on every SDK build so the
5
5
  // SDK retains its own version identity when bundled into another package.
6
- const SDK_VERSION = '2.10.0-next.4';
6
+ const SDK_VERSION = '2.10.0-next.6';
7
7
 
8
8
  exports.SDK_VERSION = SDK_VERSION;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.10.0-next.4";
1
+ export declare const SDK_VERSION = "2.10.0-next.6";
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/embed-version.mjs — DO NOT EDIT.
2
2
  // Source of truth: package.json. Regenerated on every SDK build so the
3
3
  // SDK retains its own version identity when bundled into another package.
4
- export const SDK_VERSION = '2.10.0-next.4';
4
+ export const SDK_VERSION = '2.10.0-next.6';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.10.0-next.4",
3
+ "version": "2.10.0-next.6",
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.10.0-next.4",
41
- "@superdoc/sdk-darwin-x64": "2.10.0-next.4",
42
- "@superdoc/sdk-linux-x64": "2.10.0-next.4",
43
- "@superdoc/sdk-linux-arm64": "2.10.0-next.4",
44
- "@superdoc/sdk-windows-x64": "2.10.0-next.4"
40
+ "@superdoc/sdk-darwin-arm64": "2.10.0-next.6",
41
+ "@superdoc/sdk-darwin-x64": "2.10.0-next.6",
42
+ "@superdoc/sdk-linux-x64": "2.10.0-next.6",
43
+ "@superdoc/sdk-linux-arm64": "2.10.0-next.6",
44
+ "@superdoc/sdk-windows-x64": "2.10.0-next.6"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
@@ -50,7 +50,8 @@
50
50
  "build": "rm -rf dist && node scripts/embed-version.mjs && node scripts/embed-prompts.mjs && node scripts/embed-tools.mjs && tsc && pnpm run typecheck:consumer && 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
- "typecheck:consumer": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext ../../../../tests/consumer-typecheck/src/sdk-per-open-collaboration-auth.mts",
53
+ "typecheck:consumer": "tsc --noEmit --strict --skipLibCheck --target ES2022 --module NodeNext --moduleResolution NodeNext ../../../../tests/consumer-typecheck/src/sdk-per-open-collaboration-auth.mts ../../../../tests/consumer-typecheck/src/sdk-replace-file.mts",
54
+ "test:agent-actions": "bun test src/__tests__/actions.test.ts",
54
55
  "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",
55
56
  "smoke:product-action": "node scripts/product-action-smoke.mjs"
56
57
  }