@superdoc/sdk 2.18.0-next.1 → 2.18.0-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -54,6 +54,9 @@ class BoundRuntime {
54
54
  supportsDocumentFacts() {
55
55
  return !this.closed && this.runtime.supportsDocumentFacts(this.sessionId);
56
56
  }
57
+ usesCliDocumentFacts() {
58
+ return !this.closed && this.runtime.usesCliDocumentFacts(this.sessionId);
59
+ }
57
60
  async readRevision() {
58
61
  if (this.closed)
59
62
  throw new errors.SuperDocCliError('Document handle is closed; cannot read its revision.', { code: 'DOCUMENT_CLOSED' });
@@ -84,7 +87,7 @@ class SuperDocDocumentCore {
84
87
  this.client = client$1;
85
88
  attachBoundDocApi(this, client.createBoundDocApi(this.boundRuntime));
86
89
  documentEvidence.registerDocumentRevisionReader(this, () => this.boundRuntime.readRevision());
87
- documentEvidence.registerDocumentFactCapability(this, () => this.boundRuntime.supportsDocumentFacts());
90
+ documentEvidence.registerDocumentFactCapability(this, () => this.boundRuntime.supportsDocumentFacts(), () => this.boundRuntime.usesCliDocumentFacts());
88
91
  }
89
92
  get sessionId() {
90
93
  return this._sessionId;
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ declare class BoundRuntime implements RuntimeInvoker {
14
14
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: InvokeOptions): Promise<TData>;
15
15
  replaceFile(source: ReplaceFileSource, options?: ReplaceFileOptions): Promise<ReplaceFileResult>;
16
16
  supportsDocumentFacts(): boolean;
17
+ usesCliDocumentFacts(): boolean;
17
18
  readRevision(): Promise<string | undefined>;
18
19
  markClosed(): void;
19
20
  }
package/dist/index.js CHANGED
@@ -42,6 +42,9 @@ class BoundRuntime {
42
42
  supportsDocumentFacts() {
43
43
  return !this.closed && this.runtime.supportsDocumentFacts(this.sessionId);
44
44
  }
45
+ usesCliDocumentFacts() {
46
+ return !this.closed && this.runtime.usesCliDocumentFacts(this.sessionId);
47
+ }
45
48
  async readRevision() {
46
49
  if (this.closed)
47
50
  throw new SuperDocCliError('Document handle is closed; cannot read its revision.', { code: 'DOCUMENT_CLOSED' });
@@ -72,7 +75,7 @@ class SuperDocDocumentCore {
72
75
  this.client = client;
73
76
  attachBoundDocApi(this, createBoundDocApi(this.boundRuntime));
74
77
  registerDocumentRevisionReader(this, () => this.boundRuntime.readRevision());
75
- registerDocumentFactCapability(this, () => this.boundRuntime.supportsDocumentFacts());
78
+ registerDocumentFactCapability(this, () => this.boundRuntime.supportsDocumentFacts(), () => this.boundRuntime.usesCliDocumentFacts());
76
79
  }
77
80
  get sessionId() {
78
81
  return this._sessionId;
@@ -18,17 +18,23 @@ function getDocumentRevisionReader(document) {
18
18
  return revisionReaders.get(document);
19
19
  }
20
20
  const factCapabilities = new WeakMap();
21
- function registerDocumentFactCapability(document, supported) {
21
+ const summaryCapabilities = new WeakMap();
22
+ function registerDocumentFactCapability(document, supported, summaryCounts) {
22
23
  factCapabilities.set(document, supported);
24
+ if (summaryCounts)
25
+ summaryCapabilities.set(document, summaryCounts);
23
26
  }
24
27
  function supportsDocumentFacts(document) {
25
28
  return factCapabilities.get(document)?.() === true;
26
29
  }
30
+ function requiresSummaryCounts(document) {
31
+ return summaryCapabilities.get(document)?.() === true;
32
+ }
27
33
  function forwardDocumentEvidence(source, target) {
28
34
  const reader = getDocumentRevisionReader(source);
29
35
  if (reader)
30
36
  registerDocumentRevisionReader(target, reader);
31
- registerDocumentFactCapability(target, () => supportsDocumentFacts(source));
37
+ registerDocumentFactCapability(target, () => supportsDocumentFacts(source), () => requiresSummaryCounts(source));
32
38
  }
33
39
 
34
40
  exports.forwardDocumentEvidence = forwardDocumentEvidence;
@@ -37,4 +43,5 @@ exports.getMutationRevision = getMutationRevision;
37
43
  exports.registerDocumentFactCapability = registerDocumentFactCapability;
38
44
  exports.registerDocumentRevisionReader = registerDocumentRevisionReader;
39
45
  exports.registerMutationRevision = registerMutationRevision;
46
+ exports.requiresSummaryCounts = requiresSummaryCounts;
40
47
  exports.supportsDocumentFacts = supportsDocumentFacts;
@@ -7,7 +7,8 @@ export declare function registerMutationRevision(result: unknown, revision: Muta
7
7
  export declare function getMutationRevision(result: unknown): MutationRevision | undefined;
8
8
  export declare function registerDocumentRevisionReader(document: object, reader: RevisionReader): void;
9
9
  export declare function getDocumentRevisionReader(document: object): RevisionReader | undefined;
10
- export declare function registerDocumentFactCapability(document: object, supported: () => boolean): void;
10
+ export declare function registerDocumentFactCapability(document: object, supported: () => boolean, summaryCounts?: () => boolean): void;
11
11
  export declare function supportsDocumentFacts(document: object): boolean;
12
+ export declare function requiresSummaryCounts(document: object): boolean;
12
13
  export declare function forwardDocumentEvidence(source: object, target: object): void;
13
14
  export {};
@@ -16,15 +16,21 @@ export function getDocumentRevisionReader(document) {
16
16
  return revisionReaders.get(document);
17
17
  }
18
18
  const factCapabilities = new WeakMap();
19
- export function registerDocumentFactCapability(document, supported) {
19
+ const summaryCapabilities = new WeakMap();
20
+ export function registerDocumentFactCapability(document, supported, summaryCounts) {
20
21
  factCapabilities.set(document, supported);
22
+ if (summaryCounts)
23
+ summaryCapabilities.set(document, summaryCounts);
21
24
  }
22
25
  export function supportsDocumentFacts(document) {
23
26
  return factCapabilities.get(document)?.() === true;
24
27
  }
28
+ export function requiresSummaryCounts(document) {
29
+ return summaryCapabilities.get(document)?.() === true;
30
+ }
25
31
  export function forwardDocumentEvidence(source, target) {
26
32
  const reader = getDocumentRevisionReader(source);
27
33
  if (reader)
28
34
  registerDocumentRevisionReader(target, reader);
29
- registerDocumentFactCapability(target, () => supportsDocumentFacts(source));
35
+ registerDocumentFactCapability(target, () => supportsDocumentFacts(source), () => requiresSummaryCounts(source));
30
36
  }
@@ -4,6 +4,7 @@ var node_child_process = require('node:child_process');
4
4
  var transportCommon = require('./transport-common.cjs');
5
5
  var errors = require('./errors.cjs');
6
6
  var documentEvidence = require('./document-evidence.cjs');
7
+ var contract = require('../generated/contract.cjs');
7
8
  var documentRpc = require('./document-rpc.cjs');
8
9
 
9
10
  const HOST_PROTOCOL_VERSION = '1.0';
@@ -106,6 +107,7 @@ class HostTransport {
106
107
  activeCalls = new Set();
107
108
  hostFeatures = new Set();
108
109
  documentRpcSessions = new Set();
110
+ cliLocalSessions = new Set();
109
111
  dirtyDocumentRpcSessions = new Set();
110
112
  documentRpcMutationStates = new Map();
111
113
  indeterminateDiscardCloses = new Set();
@@ -132,13 +134,28 @@ class HostTransport {
132
134
  await this.runWhileActive(() => this.ensureConnected());
133
135
  }
134
136
  supportsDocumentFacts(sessionId) {
135
- return (this.documentRpcSessions.has(sessionId) &&
136
- this.hostFeatures.has('document.currentRevision') &&
137
- this.hostFeatures.has('document.executionFacts') &&
138
- this.hostFeatures.has('document.mutationRevision'));
137
+ return (this.usesCliDocumentFacts(sessionId) ||
138
+ (this.documentRpcSessions.has(sessionId) &&
139
+ this.hostFeatures.has('document.currentRevision') &&
140
+ this.hostFeatures.has('document.executionFacts') &&
141
+ this.hostFeatures.has('document.mutationRevision')));
142
+ }
143
+ usesCliDocumentFacts(sessionId) {
144
+ return this.cliLocalSessions.has(sessionId) && this.hostFeatures.has('cli.invoke.localExecutionFacts');
139
145
  }
140
146
  async readDocumentRevision(sessionId) {
141
147
  return this.runWhileActive(async () => {
148
+ if (this.usesCliDocumentFacts(sessionId)) {
149
+ const revisionOperation = contract.CONTRACT.operations['doc.blocks.list'];
150
+ const page = await this.invoke(revisionOperation, {
151
+ sessionId,
152
+ offset: Number.MAX_SAFE_INTEGER,
153
+ limit: 1,
154
+ });
155
+ if (typeof page.revision !== 'string' || !page.revision || page.revision === 'unknown')
156
+ throw new errors.SuperDocCliError('CLI returned an invalid execution revision.', { code: 'HOST_PROTOCOL_ERROR' });
157
+ return page.revision;
158
+ }
142
159
  if (!this.documentRpcSessions.has(sessionId) || !this.hostFeatures.has('document.currentRevision')) {
143
160
  return undefined;
144
161
  }
@@ -359,9 +376,23 @@ class HostTransport {
359
376
  const argv = transportCommon.buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
360
377
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
361
378
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
379
+ const structuredInput = this.hostFeatures.has('cli.invoke.structuredReads')
380
+ ? transportCommon.buildStructuredReadInput(operation, params, options, argv)
381
+ : undefined;
382
+ const includeRevision = sessionId !== undefined &&
383
+ this.usesCliDocumentFacts(sessionId) &&
384
+ operation.mutates === true;
362
385
  const response = await this.sendJsonRpcRequest('cli.invoke', {
363
- argv,
364
- stdinBase64,
386
+ ...(structuredInput === undefined
387
+ ? { argv, stdinBase64 }
388
+ : {
389
+ operationId: operation.operationId,
390
+ input: structuredInput,
391
+ ...((options.timeoutMs ?? this.requestTimeoutMs) === undefined
392
+ ? {}
393
+ : { timeoutMs: options.timeoutMs ?? this.requestTimeoutMs }),
394
+ }),
395
+ ...(includeRevision ? { includeRevision: true } : {}),
365
396
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
366
397
  }, watchdogTimeout, {
367
398
  requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
@@ -373,7 +404,18 @@ class HostTransport {
373
404
  });
374
405
  }
375
406
  const resultRecord = response;
376
- return mapCliInvocationResult(operation, resultRecord.data);
407
+ const result = mapCliInvocationResult(operation, resultRecord.data);
408
+ if (operation.operationId === 'doc.open' && typeof result === 'object' && result !== null) {
409
+ const opened = result;
410
+ if (typeof opened.contextId === 'string' && opened.sessionType === 'local')
411
+ this.cliLocalSessions.add(opened.contextId);
412
+ }
413
+ if (operation.operationId === 'doc.close' && sessionId)
414
+ this.cliLocalSessions.delete(sessionId);
415
+ const revision = resultRecord.meta?.revision;
416
+ if (includeRevision && revision)
417
+ documentEvidence.registerMutationRevision(result, revision);
418
+ return result;
377
419
  }
378
420
  runWhileActive(start) {
379
421
  if (this.terminalDisposalError) {
@@ -54,6 +54,7 @@ export declare class HostTransport {
54
54
  private readonly activeCalls;
55
55
  private readonly hostFeatures;
56
56
  private readonly documentRpcSessions;
57
+ private readonly cliLocalSessions;
57
58
  private readonly dirtyDocumentRpcSessions;
58
59
  private readonly documentRpcMutationStates;
59
60
  private readonly indeterminateDiscardCloses;
@@ -63,6 +64,7 @@ export declare class HostTransport {
63
64
  } & SuperDocClientOptions);
64
65
  connect(): Promise<void>;
65
66
  supportsDocumentFacts(sessionId: string): boolean;
67
+ usesCliDocumentFacts(sessionId: string): boolean;
66
68
  readDocumentRevision(sessionId: string): Promise<string | undefined>;
67
69
  dispose(): Promise<void>;
68
70
  private disposeAfterActiveCalls;
@@ -1,7 +1,8 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { buildOperationArgv, normalizeCollaborationAuth, resolveInvocation, } from './transport-common.js';
2
+ import { buildOperationArgv, buildStructuredReadInput, normalizeCollaborationAuth, resolveInvocation, } from './transport-common.js';
3
3
  import { SuperDocCliError } from './errors.js';
4
4
  import { registerMutationRevision } from './document-evidence.js';
5
+ import { CONTRACT } from '../generated/contract.js';
5
6
  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
7
  const HOST_PROTOCOL_VERSION = '1.0';
7
8
  const CLI_HOST_REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
@@ -103,6 +104,7 @@ export class HostTransport {
103
104
  activeCalls = new Set();
104
105
  hostFeatures = new Set();
105
106
  documentRpcSessions = new Set();
107
+ cliLocalSessions = new Set();
106
108
  dirtyDocumentRpcSessions = new Set();
107
109
  documentRpcMutationStates = new Map();
108
110
  indeterminateDiscardCloses = new Set();
@@ -129,13 +131,28 @@ export class HostTransport {
129
131
  await this.runWhileActive(() => this.ensureConnected());
130
132
  }
131
133
  supportsDocumentFacts(sessionId) {
132
- return (this.documentRpcSessions.has(sessionId) &&
133
- this.hostFeatures.has('document.currentRevision') &&
134
- this.hostFeatures.has('document.executionFacts') &&
135
- this.hostFeatures.has('document.mutationRevision'));
134
+ return (this.usesCliDocumentFacts(sessionId) ||
135
+ (this.documentRpcSessions.has(sessionId) &&
136
+ this.hostFeatures.has('document.currentRevision') &&
137
+ this.hostFeatures.has('document.executionFacts') &&
138
+ this.hostFeatures.has('document.mutationRevision')));
139
+ }
140
+ usesCliDocumentFacts(sessionId) {
141
+ return this.cliLocalSessions.has(sessionId) && this.hostFeatures.has('cli.invoke.localExecutionFacts');
136
142
  }
137
143
  async readDocumentRevision(sessionId) {
138
144
  return this.runWhileActive(async () => {
145
+ if (this.usesCliDocumentFacts(sessionId)) {
146
+ const revisionOperation = CONTRACT.operations['doc.blocks.list'];
147
+ const page = await this.invoke(revisionOperation, {
148
+ sessionId,
149
+ offset: Number.MAX_SAFE_INTEGER,
150
+ limit: 1,
151
+ });
152
+ if (typeof page.revision !== 'string' || !page.revision || page.revision === 'unknown')
153
+ throw new SuperDocCliError('CLI returned an invalid execution revision.', { code: 'HOST_PROTOCOL_ERROR' });
154
+ return page.revision;
155
+ }
139
156
  if (!this.documentRpcSessions.has(sessionId) || !this.hostFeatures.has('document.currentRevision')) {
140
157
  return undefined;
141
158
  }
@@ -356,9 +373,23 @@ export class HostTransport {
356
373
  const argv = buildOperationArgv(operation, params, options, this.requestTimeoutMs, this.defaultChangeMode, this.user);
357
374
  const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
358
375
  const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
376
+ const structuredInput = this.hostFeatures.has('cli.invoke.structuredReads')
377
+ ? buildStructuredReadInput(operation, params, options, argv)
378
+ : undefined;
379
+ const includeRevision = sessionId !== undefined &&
380
+ this.usesCliDocumentFacts(sessionId) &&
381
+ operation.mutates === true;
359
382
  const response = await this.sendJsonRpcRequest('cli.invoke', {
360
- argv,
361
- stdinBase64,
383
+ ...(structuredInput === undefined
384
+ ? { argv, stdinBase64 }
385
+ : {
386
+ operationId: operation.operationId,
387
+ input: structuredInput,
388
+ ...((options.timeoutMs ?? this.requestTimeoutMs) === undefined
389
+ ? {}
390
+ : { timeoutMs: options.timeoutMs ?? this.requestTimeoutMs }),
391
+ }),
392
+ ...(includeRevision ? { includeRevision: true } : {}),
362
393
  ...(collaborationAuth === undefined ? {} : { collaborationAuth }),
363
394
  }, watchdogTimeout, {
364
395
  requestTimeoutMs: supportsRequestTimeoutMetadata ? options.timeoutMs : undefined,
@@ -370,7 +401,18 @@ export class HostTransport {
370
401
  });
371
402
  }
372
403
  const resultRecord = response;
373
- return mapCliInvocationResult(operation, resultRecord.data);
404
+ const result = mapCliInvocationResult(operation, resultRecord.data);
405
+ if (operation.operationId === 'doc.open' && typeof result === 'object' && result !== null) {
406
+ const opened = result;
407
+ if (typeof opened.contextId === 'string' && opened.sessionType === 'local')
408
+ this.cliLocalSessions.add(opened.contextId);
409
+ }
410
+ if (operation.operationId === 'doc.close' && sessionId)
411
+ this.cliLocalSessions.delete(sessionId);
412
+ const revision = resultRecord.meta?.revision;
413
+ if (includeRevision && revision)
414
+ registerMutationRevision(result, revision);
415
+ return result;
374
416
  }
375
417
  runWhileActive(start) {
376
418
  if (this.terminalDisposalError) {
@@ -60,6 +60,9 @@ class SuperDocRuntime {
60
60
  supportsDocumentFacts(sessionId) {
61
61
  return this.transport.supportsDocumentFacts(sessionId);
62
62
  }
63
+ usesCliDocumentFacts(sessionId) {
64
+ return this.transport.usesCliDocumentFacts(sessionId);
65
+ }
63
66
  async readDocumentRevision(sessionId) {
64
67
  return this.transport.readDocumentRevision(sessionId);
65
68
  }
@@ -33,6 +33,7 @@ export declare class SuperDocRuntime {
33
33
  constructor(options?: SuperDocClientOptions);
34
34
  connect(): Promise<void>;
35
35
  supportsDocumentFacts(sessionId: string): boolean;
36
+ usesCliDocumentFacts(sessionId: string): boolean;
36
37
  readDocumentRevision(sessionId: string): Promise<string | undefined>;
37
38
  dispose(): Promise<void>;
38
39
  invoke<TData = unknown>(operation: OperationSpec, params?: Record<string, unknown>, options?: TransportInvokeOptions): Promise<TData>;
@@ -57,6 +57,9 @@ export class SuperDocRuntime {
57
57
  supportsDocumentFacts(sessionId) {
58
58
  return this.transport.supportsDocumentFacts(sessionId);
59
59
  }
60
+ usesCliDocumentFacts(sessionId) {
61
+ return this.transport.usesCliDocumentFacts(sessionId);
62
+ }
60
63
  async readDocumentRevision(sessionId) {
61
64
  return this.transport.readDocumentRevision(sessionId);
62
65
  }
@@ -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.18.0-next.1';
6
+ const SDK_VERSION = '2.18.0-next.3';
7
7
 
8
8
  exports.SDK_VERSION = SDK_VERSION;
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.18.0-next.1";
1
+ export declare const SDK_VERSION = "2.18.0-next.3";
@@ -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.18.0-next.1';
4
+ export const SDK_VERSION = '2.18.0-next.3';
@@ -1,7 +1,96 @@
1
1
  'use strict';
2
2
 
3
+ var node_util = require('node:util');
3
4
  var errors = require('./errors.cjs');
4
5
 
6
+ function buildStructuredReadInput(operation, params, options, encodedArgv) {
7
+ if (!['doc.info', 'doc.blocks.list', 'doc.query.match'].includes(operation.operationId))
8
+ return undefined;
9
+ if (node_util.types.isProxy(params))
10
+ return undefined;
11
+ const prototype = Object.getPrototypeOf(params);
12
+ if (prototype !== Object.prototype && prototype !== null)
13
+ return undefined;
14
+ for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(params)))
15
+ if (descriptor.get || descriptor.set)
16
+ return undefined;
17
+ if (typeof params.sessionId !== 'string' ||
18
+ !params.sessionId ||
19
+ params.doc != null ||
20
+ options.stdinBytes !== undefined ||
21
+ options.collaborationAuth !== undefined)
22
+ return undefined;
23
+ if (!encodedArgv)
24
+ return undefined;
25
+ const flags = new Map(operation.params.filter((spec) => spec.kind !== 'doc').map((spec) => [`--${spec.flag ?? spec.name}`, spec]));
26
+ const encoded = new Map();
27
+ // Read the completed encoding; later serializers may have mutated earlier params.
28
+ for (let index = operation.commandTokens.length; index < encodedArgv.length; index += 2) {
29
+ const flag = encodedArgv[index];
30
+ const value = encodedArgv[index + 1];
31
+ if (typeof value !== 'string')
32
+ return undefined;
33
+ if (flag === '--output' || flag === '--timeout-ms')
34
+ continue;
35
+ const spec = flags.get(flag);
36
+ if (!spec || (spec.kind !== 'jsonFlag' && value.startsWith('-')))
37
+ return undefined;
38
+ const values = encoded.get(spec.name) ?? [];
39
+ values.push(value);
40
+ encoded.set(spec.name, values);
41
+ }
42
+ const input = {};
43
+ for (const spec of operation.params) {
44
+ const value = params[spec.name];
45
+ const values = encoded.get(spec.name);
46
+ if (!values) {
47
+ if (spec.required)
48
+ return undefined;
49
+ continue;
50
+ }
51
+ if (spec.type !== 'string[]' && values.length !== 1)
52
+ return undefined;
53
+ const token = values[0];
54
+ if (spec.kind === 'jsonFlag') {
55
+ // Reuse the existing encoding so custom serializers and getters run once.
56
+ if (value === null)
57
+ return undefined;
58
+ try {
59
+ input[spec.name] = JSON.parse(token);
60
+ }
61
+ catch {
62
+ return undefined;
63
+ }
64
+ continue;
65
+ }
66
+ // Coercions and unusual scalar tokens retain the existing argv validator.
67
+ if (spec.type === 'number') {
68
+ if (typeof value !== 'number' ||
69
+ !Number.isFinite(value) ||
70
+ value < 0 ||
71
+ !Number.isFinite(Number(token)) ||
72
+ Number(token) < 0)
73
+ return undefined;
74
+ input[spec.name] = Number(token);
75
+ }
76
+ else if (spec.type === 'boolean') {
77
+ if (typeof value !== 'boolean' || !['true', 'false'].includes(token))
78
+ return undefined;
79
+ input[spec.name] = token === 'true';
80
+ }
81
+ else if (spec.type === 'string[]') {
82
+ if (values.some((item) => !item || item.trim() !== item))
83
+ return undefined;
84
+ input[spec.name] = values;
85
+ }
86
+ else {
87
+ if (typeof value !== 'string' || !token || token.trim() !== token)
88
+ return undefined;
89
+ input[spec.name] = token;
90
+ }
91
+ }
92
+ return input;
93
+ }
5
94
  function normalizeCollaborationAuth(value) {
6
95
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
7
96
  throw new errors.SuperDocCliError('collaborationAuth must be an object.', { code: 'INVALID_ARGUMENT' });
@@ -192,5 +281,6 @@ function buildOperationArgv(operation, params, options, runtimeTimeoutMs, defaul
192
281
 
193
282
  exports.applyOperationParamAliases = applyOperationParamAliases;
194
283
  exports.buildOperationArgv = buildOperationArgv;
284
+ exports.buildStructuredReadInput = buildStructuredReadInput;
195
285
  exports.normalizeCollaborationAuth = normalizeCollaborationAuth;
196
286
  exports.resolveInvocation = resolveInvocation;
@@ -12,6 +12,7 @@ export interface OperationSpec {
12
12
  readonly commandTokens: readonly string[];
13
13
  readonly params: readonly OperationParamSpec[];
14
14
  }
15
+ export declare function buildStructuredReadInput(operation: OperationSpec, params: Record<string, unknown>, options: TransportInvokeOptions, encodedArgv?: readonly string[]): Record<string, unknown> | undefined;
15
16
  export interface InvokeOptions {
16
17
  timeoutMs?: number;
17
18
  stdinBytes?: Uint8Array;
@@ -1,4 +1,93 @@
1
+ import { types as utilTypes } from 'node:util';
1
2
  import { SuperDocCliError } from './errors.js';
3
+ export function buildStructuredReadInput(operation, params, options, encodedArgv) {
4
+ if (!['doc.info', 'doc.blocks.list', 'doc.query.match'].includes(operation.operationId))
5
+ return undefined;
6
+ if (utilTypes.isProxy(params))
7
+ return undefined;
8
+ const prototype = Object.getPrototypeOf(params);
9
+ if (prototype !== Object.prototype && prototype !== null)
10
+ return undefined;
11
+ for (const descriptor of Object.values(Object.getOwnPropertyDescriptors(params)))
12
+ if (descriptor.get || descriptor.set)
13
+ return undefined;
14
+ if (typeof params.sessionId !== 'string' ||
15
+ !params.sessionId ||
16
+ params.doc != null ||
17
+ options.stdinBytes !== undefined ||
18
+ options.collaborationAuth !== undefined)
19
+ return undefined;
20
+ if (!encodedArgv)
21
+ return undefined;
22
+ const flags = new Map(operation.params.filter((spec) => spec.kind !== 'doc').map((spec) => [`--${spec.flag ?? spec.name}`, spec]));
23
+ const encoded = new Map();
24
+ // Read the completed encoding; later serializers may have mutated earlier params.
25
+ for (let index = operation.commandTokens.length; index < encodedArgv.length; index += 2) {
26
+ const flag = encodedArgv[index];
27
+ const value = encodedArgv[index + 1];
28
+ if (typeof value !== 'string')
29
+ return undefined;
30
+ if (flag === '--output' || flag === '--timeout-ms')
31
+ continue;
32
+ const spec = flags.get(flag);
33
+ if (!spec || (spec.kind !== 'jsonFlag' && value.startsWith('-')))
34
+ return undefined;
35
+ const values = encoded.get(spec.name) ?? [];
36
+ values.push(value);
37
+ encoded.set(spec.name, values);
38
+ }
39
+ const input = {};
40
+ for (const spec of operation.params) {
41
+ const value = params[spec.name];
42
+ const values = encoded.get(spec.name);
43
+ if (!values) {
44
+ if (spec.required)
45
+ return undefined;
46
+ continue;
47
+ }
48
+ if (spec.type !== 'string[]' && values.length !== 1)
49
+ return undefined;
50
+ const token = values[0];
51
+ if (spec.kind === 'jsonFlag') {
52
+ // Reuse the existing encoding so custom serializers and getters run once.
53
+ if (value === null)
54
+ return undefined;
55
+ try {
56
+ input[spec.name] = JSON.parse(token);
57
+ }
58
+ catch {
59
+ return undefined;
60
+ }
61
+ continue;
62
+ }
63
+ // Coercions and unusual scalar tokens retain the existing argv validator.
64
+ if (spec.type === 'number') {
65
+ if (typeof value !== 'number' ||
66
+ !Number.isFinite(value) ||
67
+ value < 0 ||
68
+ !Number.isFinite(Number(token)) ||
69
+ Number(token) < 0)
70
+ return undefined;
71
+ input[spec.name] = Number(token);
72
+ }
73
+ else if (spec.type === 'boolean') {
74
+ if (typeof value !== 'boolean' || !['true', 'false'].includes(token))
75
+ return undefined;
76
+ input[spec.name] = token === 'true';
77
+ }
78
+ else if (spec.type === 'string[]') {
79
+ if (values.some((item) => !item || item.trim() !== item))
80
+ return undefined;
81
+ input[spec.name] = values;
82
+ }
83
+ else {
84
+ if (typeof value !== 'string' || !token || token.trim() !== token)
85
+ return undefined;
86
+ input[spec.name] = token;
87
+ }
88
+ }
89
+ return input;
90
+ }
2
91
  export function normalizeCollaborationAuth(value) {
3
92
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
4
93
  throw new SuperDocCliError('collaborationAuth must be an object.', { code: 'INVALID_ARGUMENT' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superdoc/sdk",
3
- "version": "2.18.0-next.1",
3
+ "version": "2.18.0-next.3",
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",
@@ -38,11 +38,11 @@
38
38
  "typescript": "^5.9.2"
39
39
  },
40
40
  "optionalDependencies": {
41
- "@superdoc/sdk-darwin-arm64": "2.18.0-next.1",
42
- "@superdoc/sdk-darwin-x64": "2.18.0-next.1",
43
- "@superdoc/sdk-linux-x64": "2.18.0-next.1",
44
- "@superdoc/sdk-linux-arm64": "2.18.0-next.1",
45
- "@superdoc/sdk-windows-x64": "2.18.0-next.1"
41
+ "@superdoc/sdk-darwin-arm64": "2.18.0-next.3",
42
+ "@superdoc/sdk-darwin-x64": "2.18.0-next.3",
43
+ "@superdoc/sdk-linux-x64": "2.18.0-next.3",
44
+ "@superdoc/sdk-linux-arm64": "2.18.0-next.3",
45
+ "@superdoc/sdk-windows-x64": "2.18.0-next.3"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"
@@ -53,7 +53,7 @@
53
53
  "typecheck": "tsc --noEmit",
54
54
  "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 ../../../../tests/consumer-typecheck/src/sdk-agent-evidence.mts ../../../../tests/consumer-typecheck/src/sdk-custom-actions.mts ../../../../tests/consumer-typecheck/src/sd4807-sdk-inspect.mts ../../../../tests/consumer-typecheck/src/sdk-tracked-only-policy.mts ../../../../tests/consumer-typecheck/src/sdk-tracked-review-route.mts ../../../../tests/consumer-typecheck/src/sd5266-sdk-placeholder-null.mts",
55
55
  "test:agent-actions": "bun test src/__tests__/actions.test.ts src/__tests__/agent-runtime.test.ts src/__tests__/custom-actions.test.ts src/__tests__/textbox-snapshot.test.ts",
56
- "test:document-host": "bun test src/runtime/__tests__/host-spawn-args.test.ts src/runtime/__tests__/document-rpc.test.ts src/__tests__/tracked-review-route.test.ts src/__tests__/structured-document-rpc.e2e.test.ts src/__tests__/request-timeout-ms.e2e.test.ts src/__tests__/agent-evidence.e2e.test.ts src/__tests__/agent-apply.e2e.test.ts src/__tests__/find-text.e2e.test.ts src/__tests__/custom-actions.e2e.test.ts",
56
+ "test:document-host": "bun test src/__tests__/default-cli-optimization.e2e.test.ts src/runtime/__tests__/host-spawn-args.test.ts src/runtime/__tests__/document-rpc.test.ts src/__tests__/tracked-review-route.test.ts src/__tests__/structured-document-rpc.e2e.test.ts src/__tests__/request-timeout-ms.e2e.test.ts src/__tests__/agent-evidence.e2e.test.ts src/__tests__/agent-apply.e2e.test.ts src/__tests__/find-text.e2e.test.ts src/__tests__/custom-actions.e2e.test.ts",
57
57
  "smoke:product-action": "node scripts/product-action-smoke.mjs"
58
58
  }
59
59
  }