@superdoc-dev/sdk 1.0.0-alpha.3 → 1.0.0-alpha.4
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/generated/client.d.ts +1790 -0
- package/dist/generated/client.d.ts.map +1 -0
- package/dist/generated/client.js +66 -0
- package/dist/generated/contract.d.ts +13676 -0
- package/dist/generated/contract.d.ts.map +1 -0
- package/dist/generated/contract.js +17809 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/runtime/embedded-cli.d.ts +5 -0
- package/dist/runtime/embedded-cli.d.ts.map +1 -0
- package/dist/runtime/embedded-cli.js +94 -0
- package/dist/runtime/errors.d.ts +17 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +18 -0
- package/dist/runtime/host.d.ts +36 -0
- package/dist/runtime/host.d.ts.map +1 -0
- package/dist/runtime/host.js +345 -0
- package/dist/runtime/process.d.ts +16 -0
- package/dist/runtime/process.d.ts.map +1 -0
- package/dist/runtime/process.js +27 -0
- package/dist/runtime/transport-common.d.ts +44 -0
- package/dist/runtime/transport-common.d.ts.map +1 -0
- package/dist/runtime/transport-common.js +65 -0
- package/dist/skills.d.ts +25 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +140 -0
- package/dist/tools.d.ts +113 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +360 -0
- package/package.json +19 -10
- package/tools/catalog.json +17128 -0
- package/tools/tool-name-map.json +96 -0
- package/tools/tools-policy.json +100 -0
- package/tools/tools.anthropic.json +3275 -0
- package/tools/tools.generic.json +16573 -0
- package/tools/tools.openai.json +3557 -0
- package/tools/tools.vercel.json +3557 -0
- package/skills/editing-docx.md +0 -157
- package/src/__tests__/skills.test.ts +0 -166
- package/src/__tests__/tools.test.ts +0 -96
- package/src/generated/client.ts +0 -3643
- package/src/generated/contract.ts +0 -15952
- package/src/index.ts +0 -87
- package/src/runtime/__tests__/process.test.ts +0 -38
- package/src/runtime/__tests__/transport-common.test.ts +0 -174
- package/src/runtime/embedded-cli.ts +0 -109
- package/src/runtime/errors.ts +0 -30
- package/src/runtime/host.ts +0 -481
- package/src/runtime/process.ts +0 -45
- package/src/runtime/transport-common.ts +0 -169
- package/src/skills.ts +0 -195
- package/src/tools.ts +0 -701
package/src/runtime/host.ts
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
-
import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
|
|
3
|
-
import {
|
|
4
|
-
buildOperationArgv,
|
|
5
|
-
resolveInvocation,
|
|
6
|
-
type ChangeMode,
|
|
7
|
-
type InvokeOptions,
|
|
8
|
-
type OperationSpec,
|
|
9
|
-
type SuperDocClientOptions,
|
|
10
|
-
} from './transport-common';
|
|
11
|
-
import { SuperDocCliError } from './errors';
|
|
12
|
-
|
|
13
|
-
type PendingRequest = {
|
|
14
|
-
resolve: (value: unknown) => void;
|
|
15
|
-
reject: (error: SuperDocCliError) => void;
|
|
16
|
-
timer: NodeJS.Timeout;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type JsonRpcErrorData = {
|
|
20
|
-
cliCode?: unknown;
|
|
21
|
-
message?: unknown;
|
|
22
|
-
details?: unknown;
|
|
23
|
-
exitCode?: unknown;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type JsonRpcError = {
|
|
27
|
-
code: number;
|
|
28
|
-
message: string;
|
|
29
|
-
data?: JsonRpcErrorData;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const HOST_PROTOCOL_VERSION = '1.0';
|
|
33
|
-
const REQUIRED_FEATURES = ['cli.invoke', 'host.shutdown'];
|
|
34
|
-
const CHANGE_MODES = ['direct', 'tracked'] as const;
|
|
35
|
-
|
|
36
|
-
const JSON_RPC_TIMEOUT_CODE = -32011;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Transport that communicates with a long-lived CLI host process over JSON-RPC stdio.
|
|
40
|
-
*
|
|
41
|
-
* The host process is spawned lazily on first invocation and kept alive for
|
|
42
|
-
* subsequent calls. Call {@link dispose} to gracefully shut it down.
|
|
43
|
-
*/
|
|
44
|
-
export class HostTransport {
|
|
45
|
-
private readonly cliBin: string;
|
|
46
|
-
private readonly env?: Record<string, string | undefined>;
|
|
47
|
-
private readonly startupTimeoutMs: number;
|
|
48
|
-
private readonly shutdownTimeoutMs: number;
|
|
49
|
-
private readonly requestTimeoutMs?: number;
|
|
50
|
-
private readonly watchdogTimeoutMs: number;
|
|
51
|
-
private readonly maxQueueDepth: number;
|
|
52
|
-
private readonly defaultChangeMode?: ChangeMode;
|
|
53
|
-
|
|
54
|
-
private child: ChildProcessWithoutNullStreams | null = null;
|
|
55
|
-
private stdoutReader: ReadlineInterface | null = null;
|
|
56
|
-
private readonly pending = new Map<number, PendingRequest>();
|
|
57
|
-
private nextRequestId = 1;
|
|
58
|
-
private connecting: Promise<void> | null = null;
|
|
59
|
-
private stopping = false;
|
|
60
|
-
|
|
61
|
-
constructor(options: { cliBin: string } & SuperDocClientOptions) {
|
|
62
|
-
this.cliBin = options.cliBin;
|
|
63
|
-
this.env = options.env;
|
|
64
|
-
|
|
65
|
-
this.startupTimeoutMs = options.startupTimeoutMs ?? 5_000;
|
|
66
|
-
this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 5_000;
|
|
67
|
-
this.requestTimeoutMs = options.requestTimeoutMs;
|
|
68
|
-
this.watchdogTimeoutMs = options.watchdogTimeoutMs ?? 30_000;
|
|
69
|
-
this.maxQueueDepth = options.maxQueueDepth ?? 100;
|
|
70
|
-
if (options.defaultChangeMode != null && !CHANGE_MODES.includes(options.defaultChangeMode)) {
|
|
71
|
-
throw new SuperDocCliError('defaultChangeMode must be "direct" or "tracked".', {
|
|
72
|
-
code: 'INVALID_ARGUMENT',
|
|
73
|
-
details: { defaultChangeMode: options.defaultChangeMode },
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
this.defaultChangeMode = options.defaultChangeMode;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async connect(): Promise<void> {
|
|
80
|
-
await this.ensureConnected();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async dispose(): Promise<void> {
|
|
84
|
-
if (!this.child) return;
|
|
85
|
-
|
|
86
|
-
this.stopping = true;
|
|
87
|
-
|
|
88
|
-
const child = this.child;
|
|
89
|
-
try {
|
|
90
|
-
await this.sendJsonRpcRequest('host.shutdown', {}, this.shutdownTimeoutMs);
|
|
91
|
-
} catch {
|
|
92
|
-
// ignore and force shutdown below
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
await new Promise<void>((resolve) => {
|
|
96
|
-
const timer = setTimeout(() => {
|
|
97
|
-
child.kill('SIGKILL');
|
|
98
|
-
resolve();
|
|
99
|
-
}, this.shutdownTimeoutMs);
|
|
100
|
-
|
|
101
|
-
child.once('close', () => {
|
|
102
|
-
clearTimeout(timer);
|
|
103
|
-
resolve();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
this.cleanupProcess(null);
|
|
108
|
-
this.stopping = false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async invoke<TData extends Record<string, unknown>>(
|
|
112
|
-
operation: OperationSpec,
|
|
113
|
-
params: Record<string, unknown> = {},
|
|
114
|
-
options: InvokeOptions = {},
|
|
115
|
-
): Promise<TData> {
|
|
116
|
-
await this.ensureConnected();
|
|
117
|
-
|
|
118
|
-
const argv = buildOperationArgv(operation, params, options, this.requestTimeoutMs, false, this.defaultChangeMode);
|
|
119
|
-
const stdinBase64 = options.stdinBytes ? Buffer.from(options.stdinBytes).toString('base64') : '';
|
|
120
|
-
const watchdogTimeout = this.resolveWatchdogTimeout(options.timeoutMs);
|
|
121
|
-
|
|
122
|
-
const response = await this.sendJsonRpcRequest(
|
|
123
|
-
'cli.invoke',
|
|
124
|
-
{
|
|
125
|
-
argv,
|
|
126
|
-
stdinBase64,
|
|
127
|
-
},
|
|
128
|
-
watchdogTimeout,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
if (typeof response !== 'object' || response == null || Array.isArray(response)) {
|
|
132
|
-
throw new SuperDocCliError('Host returned invalid cli.invoke result.', {
|
|
133
|
-
code: 'HOST_PROTOCOL_ERROR',
|
|
134
|
-
details: { result: response },
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const resultRecord = response as Record<string, unknown>;
|
|
139
|
-
return resultRecord.data as TData;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private async ensureConnected(): Promise<void> {
|
|
143
|
-
if (this.child && !this.child.killed) {
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (this.connecting) {
|
|
148
|
-
await this.connecting;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
this.connecting = this.startHostProcess();
|
|
153
|
-
try {
|
|
154
|
-
await this.connecting;
|
|
155
|
-
} finally {
|
|
156
|
-
this.connecting = null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private async startHostProcess(): Promise<void> {
|
|
161
|
-
const { command, prefixArgs } = resolveInvocation(this.cliBin);
|
|
162
|
-
const args = [...prefixArgs, 'host', '--stdio'];
|
|
163
|
-
|
|
164
|
-
const child = spawn(command, args, {
|
|
165
|
-
env: {
|
|
166
|
-
...process.env,
|
|
167
|
-
...(this.env ?? {}),
|
|
168
|
-
},
|
|
169
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
this.child = child;
|
|
173
|
-
|
|
174
|
-
const stdoutReader = createInterface({
|
|
175
|
-
input: child.stdout,
|
|
176
|
-
crlfDelay: Number.POSITIVE_INFINITY,
|
|
177
|
-
});
|
|
178
|
-
this.stdoutReader = stdoutReader;
|
|
179
|
-
|
|
180
|
-
stdoutReader.on('line', (line) => {
|
|
181
|
-
this.onStdoutLine(line);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
child.stderr.on('data', () => {
|
|
185
|
-
// stderr is intentionally ignored in host mode unless process exits unexpectedly
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
child.on('error', (error) => {
|
|
189
|
-
this.handleDisconnect(
|
|
190
|
-
new SuperDocCliError('Host process failed.', {
|
|
191
|
-
code: 'HOST_DISCONNECTED',
|
|
192
|
-
details: {
|
|
193
|
-
message: error instanceof Error ? error.message : String(error),
|
|
194
|
-
},
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
child.on('close', (code, signal) => {
|
|
200
|
-
const isExpectedClose = this.stopping;
|
|
201
|
-
if (isExpectedClose) {
|
|
202
|
-
this.cleanupProcess(null);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
this.handleDisconnect(
|
|
207
|
-
new SuperDocCliError('Host process disconnected.', {
|
|
208
|
-
code: 'HOST_DISCONNECTED',
|
|
209
|
-
details: {
|
|
210
|
-
exitCode: code,
|
|
211
|
-
signal,
|
|
212
|
-
},
|
|
213
|
-
}),
|
|
214
|
-
);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const capabilities = await this.sendJsonRpcRequest('host.capabilities', {}, this.startupTimeoutMs);
|
|
219
|
-
this.assertCapabilities(capabilities);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
const normalized =
|
|
222
|
-
error instanceof SuperDocCliError
|
|
223
|
-
? error
|
|
224
|
-
: new SuperDocCliError('Host handshake failed.', {
|
|
225
|
-
code: 'HOST_HANDSHAKE_FAILED',
|
|
226
|
-
details: {
|
|
227
|
-
message: error instanceof Error ? error.message : String(error),
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
this.handleDisconnect(normalized);
|
|
231
|
-
throw normalized;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private assertCapabilities(response: unknown): void {
|
|
236
|
-
if (typeof response !== 'object' || response == null || Array.isArray(response)) {
|
|
237
|
-
throw new SuperDocCliError('Host capabilities response is invalid.', {
|
|
238
|
-
code: 'HOST_HANDSHAKE_FAILED',
|
|
239
|
-
details: { response },
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const record = response as Record<string, unknown>;
|
|
244
|
-
const protocolVersion = record.protocolVersion;
|
|
245
|
-
const features = record.features;
|
|
246
|
-
|
|
247
|
-
if (protocolVersion !== HOST_PROTOCOL_VERSION) {
|
|
248
|
-
throw new SuperDocCliError('Host protocol version is unsupported.', {
|
|
249
|
-
code: 'HOST_HANDSHAKE_FAILED',
|
|
250
|
-
details: {
|
|
251
|
-
expected: HOST_PROTOCOL_VERSION,
|
|
252
|
-
actual: protocolVersion,
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (!Array.isArray(features) || features.some((feature) => typeof feature !== 'string')) {
|
|
258
|
-
throw new SuperDocCliError('Host capabilities.features must be a string array.', {
|
|
259
|
-
code: 'HOST_HANDSHAKE_FAILED',
|
|
260
|
-
details: { features },
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for (const requiredFeature of REQUIRED_FEATURES) {
|
|
265
|
-
if (!features.includes(requiredFeature)) {
|
|
266
|
-
throw new SuperDocCliError(`Host does not support required feature: ${requiredFeature}`, {
|
|
267
|
-
code: 'HOST_HANDSHAKE_FAILED',
|
|
268
|
-
details: { features },
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private resolveWatchdogTimeout(timeoutMsOverride: number | undefined): number {
|
|
275
|
-
if (timeoutMsOverride != null) {
|
|
276
|
-
return Math.max(this.watchdogTimeoutMs, timeoutMsOverride + 1_000);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (this.requestTimeoutMs != null) {
|
|
280
|
-
return Math.max(this.watchdogTimeoutMs, this.requestTimeoutMs + 1_000);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return this.watchdogTimeoutMs;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private async sendJsonRpcRequest(method: string, params: unknown, watchdogTimeoutMs: number): Promise<unknown> {
|
|
287
|
-
const child = this.child;
|
|
288
|
-
if (!child || !child.stdin.writable) {
|
|
289
|
-
throw new SuperDocCliError('Host process is not available.', {
|
|
290
|
-
code: 'HOST_DISCONNECTED',
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (this.pending.size >= this.maxQueueDepth) {
|
|
295
|
-
throw new SuperDocCliError('Host request queue is full.', {
|
|
296
|
-
code: 'HOST_QUEUE_FULL',
|
|
297
|
-
details: {
|
|
298
|
-
maxQueueDepth: this.maxQueueDepth,
|
|
299
|
-
},
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const id = this.nextRequestId;
|
|
304
|
-
this.nextRequestId += 1;
|
|
305
|
-
|
|
306
|
-
const payload = JSON.stringify({
|
|
307
|
-
jsonrpc: '2.0',
|
|
308
|
-
id,
|
|
309
|
-
method,
|
|
310
|
-
params,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const promise = new Promise<unknown>((resolve, reject) => {
|
|
314
|
-
const timer = setTimeout(() => {
|
|
315
|
-
this.pending.delete(id);
|
|
316
|
-
|
|
317
|
-
const timeoutError = new SuperDocCliError(`Host watchdog timed out waiting for ${method}.`, {
|
|
318
|
-
code: 'HOST_TIMEOUT',
|
|
319
|
-
details: {
|
|
320
|
-
method,
|
|
321
|
-
timeoutMs: watchdogTimeoutMs,
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
reject(timeoutError);
|
|
326
|
-
|
|
327
|
-
this.handleDisconnect(
|
|
328
|
-
new SuperDocCliError('Host watchdog timeout; host process will be restarted on next request.', {
|
|
329
|
-
code: 'HOST_DISCONNECTED',
|
|
330
|
-
details: {
|
|
331
|
-
method,
|
|
332
|
-
timeoutMs: watchdogTimeoutMs,
|
|
333
|
-
},
|
|
334
|
-
}),
|
|
335
|
-
);
|
|
336
|
-
}, watchdogTimeoutMs);
|
|
337
|
-
|
|
338
|
-
this.pending.set(id, {
|
|
339
|
-
resolve,
|
|
340
|
-
reject,
|
|
341
|
-
timer,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
child.stdin.write(`${payload}\n`, (error) => {
|
|
345
|
-
if (!error) return;
|
|
346
|
-
|
|
347
|
-
const pending = this.pending.get(id);
|
|
348
|
-
if (!pending) return;
|
|
349
|
-
|
|
350
|
-
clearTimeout(pending.timer);
|
|
351
|
-
this.pending.delete(id);
|
|
352
|
-
reject(
|
|
353
|
-
new SuperDocCliError('Failed to write request to host process.', {
|
|
354
|
-
code: 'HOST_DISCONNECTED',
|
|
355
|
-
details: {
|
|
356
|
-
method,
|
|
357
|
-
message: error.message,
|
|
358
|
-
},
|
|
359
|
-
}),
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
return promise;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private onStdoutLine(line: string): void {
|
|
368
|
-
let parsed: unknown;
|
|
369
|
-
try {
|
|
370
|
-
parsed = JSON.parse(line);
|
|
371
|
-
} catch {
|
|
372
|
-
// Ignore non-protocol stdout noise (for example telemetry/logging lines).
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (typeof parsed !== 'object' || parsed == null || Array.isArray(parsed)) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const record = parsed as Record<string, unknown>;
|
|
381
|
-
if (record.jsonrpc !== '2.0') {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if ('method' in record && !('id' in record)) {
|
|
386
|
-
// Notification; reserved for future eventing.
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const idRaw = record.id;
|
|
391
|
-
if (typeof idRaw !== 'number') {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const pending = this.pending.get(idRaw);
|
|
396
|
-
if (!pending) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
clearTimeout(pending.timer);
|
|
401
|
-
this.pending.delete(idRaw);
|
|
402
|
-
|
|
403
|
-
if ('error' in record) {
|
|
404
|
-
pending.reject(this.mapJsonRpcError(record.error));
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
pending.resolve(record.result);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private mapJsonRpcError(rawError: unknown): SuperDocCliError {
|
|
412
|
-
if (typeof rawError !== 'object' || rawError == null || Array.isArray(rawError)) {
|
|
413
|
-
return new SuperDocCliError('Host returned an unknown JSON-RPC error.', {
|
|
414
|
-
code: 'HOST_PROTOCOL_ERROR',
|
|
415
|
-
details: { error: rawError },
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const error = rawError as JsonRpcError;
|
|
420
|
-
|
|
421
|
-
const data = error.data as JsonRpcErrorData | undefined;
|
|
422
|
-
const cliCode = typeof data?.cliCode === 'string' ? data.cliCode : undefined;
|
|
423
|
-
const cliMessage = typeof data?.message === 'string' ? data.message : undefined;
|
|
424
|
-
const exitCode = typeof data?.exitCode === 'number' ? data.exitCode : undefined;
|
|
425
|
-
|
|
426
|
-
if (cliCode) {
|
|
427
|
-
return new SuperDocCliError(cliMessage ?? error.message ?? 'Command failed.', {
|
|
428
|
-
code: cliCode,
|
|
429
|
-
details: data?.details,
|
|
430
|
-
exitCode,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (error.code === JSON_RPC_TIMEOUT_CODE) {
|
|
435
|
-
return new SuperDocCliError(error.message, {
|
|
436
|
-
code: 'TIMEOUT',
|
|
437
|
-
details: data,
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
return new SuperDocCliError(error.message, {
|
|
442
|
-
code: 'COMMAND_FAILED',
|
|
443
|
-
details: data,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
private handleDisconnect(error: SuperDocCliError): void {
|
|
448
|
-
this.cleanupProcess(error);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private cleanupProcess(error: SuperDocCliError | null): void {
|
|
452
|
-
const child = this.child;
|
|
453
|
-
if (child) {
|
|
454
|
-
child.removeAllListeners();
|
|
455
|
-
child.kill('SIGKILL');
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
this.child = null;
|
|
459
|
-
|
|
460
|
-
if (this.stdoutReader) {
|
|
461
|
-
this.stdoutReader.removeAllListeners();
|
|
462
|
-
this.stdoutReader.close();
|
|
463
|
-
this.stdoutReader = null;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const pendingEntries = Array.from(this.pending.values());
|
|
467
|
-
this.pending.clear();
|
|
468
|
-
|
|
469
|
-
if (!error) {
|
|
470
|
-
for (const pending of pendingEntries) {
|
|
471
|
-
clearTimeout(pending.timer);
|
|
472
|
-
}
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
for (const pending of pendingEntries) {
|
|
477
|
-
clearTimeout(pending.timer);
|
|
478
|
-
pending.reject(error);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
package/src/runtime/process.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { HostTransport } from './host';
|
|
2
|
-
import { resolveEmbeddedCliBinary } from './embedded-cli';
|
|
3
|
-
import type {
|
|
4
|
-
InvokeOptions,
|
|
5
|
-
OperationParamSpec,
|
|
6
|
-
OperationSpec,
|
|
7
|
-
SuperDocClientOptions,
|
|
8
|
-
} from './transport-common';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Internal runtime that delegates CLI invocations to a persistent host transport.
|
|
12
|
-
*
|
|
13
|
-
* Resolves the CLI binary and creates a {@link HostTransport} that communicates
|
|
14
|
-
* with a long-lived `superdoc host --stdio` process.
|
|
15
|
-
*/
|
|
16
|
-
export class SuperDocRuntime {
|
|
17
|
-
private readonly transport: HostTransport;
|
|
18
|
-
|
|
19
|
-
constructor(options: SuperDocClientOptions = {}) {
|
|
20
|
-
const cliBin = options.env?.SUPERDOC_CLI_BIN ?? process.env.SUPERDOC_CLI_BIN ?? resolveEmbeddedCliBinary();
|
|
21
|
-
|
|
22
|
-
this.transport = new HostTransport({
|
|
23
|
-
cliBin,
|
|
24
|
-
...options,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async connect(): Promise<void> {
|
|
29
|
-
await this.transport.connect();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async dispose(): Promise<void> {
|
|
33
|
-
await this.transport.dispose();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async invoke<TData extends Record<string, unknown>>(
|
|
37
|
-
operation: OperationSpec,
|
|
38
|
-
params: Record<string, unknown> = {},
|
|
39
|
-
options: InvokeOptions = {},
|
|
40
|
-
): Promise<TData> {
|
|
41
|
-
return this.transport.invoke<TData>(operation, params, options);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type { InvokeOptions, OperationParamSpec, OperationSpec, SuperDocClientOptions };
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { SuperDocCliError } from './errors';
|
|
2
|
-
|
|
3
|
-
/** Allowed CLI parameter value types. */
|
|
4
|
-
export type ParamType = 'string' | 'number' | 'boolean' | 'json' | 'string[]';
|
|
5
|
-
|
|
6
|
-
/** How a parameter is passed to the CLI (`doc` positional, `flag`, or `jsonFlag`). */
|
|
7
|
-
export type ParamKind = 'doc' | 'flag' | 'jsonFlag';
|
|
8
|
-
|
|
9
|
-
/** Describes a single parameter in a CLI operation. */
|
|
10
|
-
export interface OperationParamSpec {
|
|
11
|
-
readonly name: string;
|
|
12
|
-
readonly kind: ParamKind;
|
|
13
|
-
readonly flag?: string;
|
|
14
|
-
readonly type: ParamType;
|
|
15
|
-
readonly required?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Describes a CLI operation (command segments and its parameter specs). */
|
|
19
|
-
export interface OperationSpec {
|
|
20
|
-
readonly id: string;
|
|
21
|
-
readonly command: readonly string[];
|
|
22
|
-
readonly params: readonly OperationParamSpec[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Per-call options for a CLI invocation. */
|
|
26
|
-
export interface InvokeOptions {
|
|
27
|
-
/** Timeout in milliseconds forwarded to the CLI `--timeout-ms` flag. */
|
|
28
|
-
timeoutMs?: number;
|
|
29
|
-
/** Raw bytes piped to the CLI process stdin. */
|
|
30
|
-
stdinBytes?: Uint8Array;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export type ChangeMode = 'direct' | 'tracked';
|
|
34
|
-
|
|
35
|
-
/** Top-level options for creating a {@link SuperDocClient}. */
|
|
36
|
-
export interface SuperDocClientOptions {
|
|
37
|
-
/** Extra environment variables merged into the CLI process environment. */
|
|
38
|
-
env?: Record<string, string | undefined>;
|
|
39
|
-
/** Timeout in milliseconds for the host process startup handshake. */
|
|
40
|
-
startupTimeoutMs?: number;
|
|
41
|
-
/** Timeout in milliseconds for graceful host shutdown. */
|
|
42
|
-
shutdownTimeoutMs?: number;
|
|
43
|
-
/** Default per-request timeout in milliseconds. */
|
|
44
|
-
requestTimeoutMs?: number;
|
|
45
|
-
/** Idle watchdog timeout in milliseconds. */
|
|
46
|
-
watchdogTimeoutMs?: number;
|
|
47
|
-
/** Maximum number of queued requests. */
|
|
48
|
-
maxQueueDepth?: number;
|
|
49
|
-
/** Default change mode for mutation operations that support `changeMode`. */
|
|
50
|
-
defaultChangeMode?: ChangeMode;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Resolved command and prefix args for spawning the CLI. */
|
|
54
|
-
export interface CliInvocation {
|
|
55
|
-
command: string;
|
|
56
|
-
prefixArgs: string[];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function hasExtension(filePath: string, extension: string): boolean {
|
|
60
|
-
return filePath.toLowerCase().endsWith(extension);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Determine how to spawn the CLI binary based on its file extension.
|
|
65
|
-
*
|
|
66
|
-
* @param cliBin - Path to the CLI binary or script.
|
|
67
|
-
* @returns The shell command and any prefix arguments needed to execute it.
|
|
68
|
-
*/
|
|
69
|
-
export function resolveInvocation(cliBin: string): CliInvocation {
|
|
70
|
-
if (hasExtension(cliBin, '.js')) {
|
|
71
|
-
return { command: 'node', prefixArgs: [cliBin] };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (hasExtension(cliBin, '.ts')) {
|
|
75
|
-
return { command: 'bun', prefixArgs: [cliBin] };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return { command: cliBin, prefixArgs: [] };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function toCliFlag(flag: string): string {
|
|
82
|
-
return `--${flag}`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function encodeParam(args: string[], spec: OperationParamSpec, value: unknown): void {
|
|
86
|
-
if (value == null) {
|
|
87
|
-
if (spec.required) {
|
|
88
|
-
throw new SuperDocCliError(`Missing required parameter: ${spec.name}`, {
|
|
89
|
-
code: 'INVALID_ARGUMENT',
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (spec.kind === 'doc') {
|
|
96
|
-
args.push(String(value));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const flag = toCliFlag(spec.flag ?? spec.name);
|
|
101
|
-
|
|
102
|
-
if (spec.type === 'boolean') {
|
|
103
|
-
if (value === true) {
|
|
104
|
-
args.push(flag);
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (spec.type === 'string[]') {
|
|
110
|
-
if (!Array.isArray(value)) {
|
|
111
|
-
throw new SuperDocCliError(`Parameter ${spec.name} must be an array of strings.`, {
|
|
112
|
-
code: 'INVALID_ARGUMENT',
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (const item of value) {
|
|
117
|
-
args.push(flag, String(item));
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (spec.type === 'json') {
|
|
123
|
-
args.push(flag, JSON.stringify(value));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
args.push(flag, String(value));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Build the CLI argument vector for an operation invocation.
|
|
132
|
-
*
|
|
133
|
-
* @param operation - The operation spec describing the command and its parameters.
|
|
134
|
-
* @param params - User-supplied parameter values keyed by param name.
|
|
135
|
-
* @param options - Per-call invoke options (timeout, stdin).
|
|
136
|
-
* @param runtimeTimeoutMs - Default timeout from the runtime configuration.
|
|
137
|
-
* @param includeOutputFlag - Whether to append `--output json`.
|
|
138
|
-
* @returns The argument array ready to be passed to `spawn`.
|
|
139
|
-
* @throws {SuperDocCliError} With code `INVALID_ARGUMENT` if a required parameter is missing.
|
|
140
|
-
*/
|
|
141
|
-
export function buildOperationArgv(
|
|
142
|
-
operation: OperationSpec,
|
|
143
|
-
params: Record<string, unknown>,
|
|
144
|
-
options: InvokeOptions,
|
|
145
|
-
runtimeTimeoutMs: number | undefined,
|
|
146
|
-
includeOutputFlag: boolean,
|
|
147
|
-
defaultChangeMode?: ChangeMode,
|
|
148
|
-
): string[] {
|
|
149
|
-
const normalizedParams =
|
|
150
|
-
defaultChangeMode != null && params.changeMode == null && operation.params.some((param) => param.name === 'changeMode')
|
|
151
|
-
? { ...params, changeMode: defaultChangeMode }
|
|
152
|
-
: params;
|
|
153
|
-
|
|
154
|
-
const args: string[] = [...operation.command];
|
|
155
|
-
for (const param of operation.params) {
|
|
156
|
-
encodeParam(args, param, normalizedParams[param.name]);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const timeoutMs = options.timeoutMs ?? runtimeTimeoutMs;
|
|
160
|
-
if (timeoutMs != null) {
|
|
161
|
-
args.push('--timeout-ms', String(timeoutMs));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (includeOutputFlag) {
|
|
165
|
-
args.push('--output', 'json');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return args;
|
|
169
|
-
}
|