@holon-run/uxc-daemon-client 0.12.8 → 0.13.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 +392 -4
- package/dist/index.d.cts +98 -3
- package/dist/index.d.ts +98 -3
- package/dist/index.js +390 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
DaemonRpcError: () => DaemonRpcError,
|
|
34
|
-
UxcDaemonClient: () => UxcDaemonClient
|
|
34
|
+
UxcDaemonClient: () => UxcDaemonClient,
|
|
35
|
+
generateTypeScriptClient: () => generateTypeScriptClient
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
var import_node_child_process = require("child_process");
|
|
@@ -82,7 +83,38 @@ var UxcDaemonClient = class {
|
|
|
82
83
|
options: normalizeOptions(args.options)
|
|
83
84
|
});
|
|
84
85
|
}
|
|
86
|
+
async codegenSchema(args) {
|
|
87
|
+
const response = await this.request("runtime.invoke", {
|
|
88
|
+
request_id: requestId("codegen"),
|
|
89
|
+
endpoint: args.endpoint,
|
|
90
|
+
action: "codegen_schema",
|
|
91
|
+
operation_id: null,
|
|
92
|
+
args: null,
|
|
93
|
+
options: normalizeOptions(args.options)
|
|
94
|
+
});
|
|
95
|
+
if (response.kind !== "codegen_host_schema") {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Unexpected codegen response kind '${response.kind}' (expected codegen_host_schema)`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
assertCodegenHostSchema(response.data);
|
|
101
|
+
return response.data;
|
|
102
|
+
}
|
|
103
|
+
async generateTypeScriptClient(args) {
|
|
104
|
+
const schema = await this.codegenSchema({
|
|
105
|
+
endpoint: args.endpoint,
|
|
106
|
+
options: args.options
|
|
107
|
+
});
|
|
108
|
+
return generateTypeScriptClient(schema, args.emitter);
|
|
109
|
+
}
|
|
85
110
|
async subscribeStart(args) {
|
|
111
|
+
const mode = args.mode ?? (args.pollConfig ? "poll" : "stream");
|
|
112
|
+
if (mode === "poll" && !args.pollConfig) {
|
|
113
|
+
throw new Error("pollConfig is required when mode is 'poll'");
|
|
114
|
+
}
|
|
115
|
+
if (mode !== "poll" && args.pollConfig) {
|
|
116
|
+
throw new Error("pollConfig is only valid when mode is 'poll'");
|
|
117
|
+
}
|
|
86
118
|
return this.request("subscription.start", {
|
|
87
119
|
request_id: requestId("subscribe"),
|
|
88
120
|
endpoint: args.endpoint,
|
|
@@ -94,8 +126,8 @@ var UxcDaemonClient = class {
|
|
|
94
126
|
transport_hint: args.transportHint ?? null,
|
|
95
127
|
subprotocols: [],
|
|
96
128
|
initial_text_frames: [],
|
|
97
|
-
mode
|
|
98
|
-
poll_config: null,
|
|
129
|
+
mode,
|
|
130
|
+
poll_config: args.pollConfig ?? null,
|
|
99
131
|
ephemeral: args.ephemeral ?? (args.sink ?? "memory:") === "memory:",
|
|
100
132
|
options: normalizeOptions(args.options)
|
|
101
133
|
});
|
|
@@ -229,6 +261,361 @@ var UxcDaemonClient = class {
|
|
|
229
261
|
return response;
|
|
230
262
|
}
|
|
231
263
|
};
|
|
264
|
+
function generateTypeScriptClient(schema, options = {}) {
|
|
265
|
+
const packageImport = options.packageImport ?? "@holon-run/uxc-daemon-client";
|
|
266
|
+
const className = sanitizeTypeName(options.className ?? defaultClassName(schema.host.id));
|
|
267
|
+
const methodNames = /* @__PURE__ */ new Set();
|
|
268
|
+
const typeNames = /* @__PURE__ */ new Set();
|
|
269
|
+
const operationBlocks = [];
|
|
270
|
+
const typeBlocks = [];
|
|
271
|
+
for (const operation of schema.operations) {
|
|
272
|
+
if (!operation.execute || operation.help_only || operation.subscribable) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const methodName = uniqueName(
|
|
276
|
+
sanitizeMethodName(defaultMethodName(operation.id)),
|
|
277
|
+
methodNames
|
|
278
|
+
);
|
|
279
|
+
const typeName = uniqueName(
|
|
280
|
+
sanitizeTypeName(`${upperFirst(methodName)}Input`),
|
|
281
|
+
typeNames
|
|
282
|
+
);
|
|
283
|
+
const selectedInputSchema = selectOperationInputSchema(operation.input_schema);
|
|
284
|
+
const inputType = selectedInputSchema ? renderTsTypeFromSchema(selectedInputSchema, 0) : "Record<string, unknown>";
|
|
285
|
+
const inputRequired = selectedInputSchema ? hasRequiredInput(selectedInputSchema) : false;
|
|
286
|
+
typeBlocks.push(`export type ${typeName} = ${inputType};`);
|
|
287
|
+
operationBlocks.push(
|
|
288
|
+
[
|
|
289
|
+
` async ${methodName}(`,
|
|
290
|
+
inputRequired ? ` input: ${typeName},` : ` input?: ${typeName},`,
|
|
291
|
+
` options: RuntimeInvokeOptions = {},`,
|
|
292
|
+
` ): Promise<RuntimeResult<unknown>> {`,
|
|
293
|
+
` return this.client.call({`,
|
|
294
|
+
` endpoint: this.endpoint,`,
|
|
295
|
+
` operation: ${JSON.stringify(operation.id)},`,
|
|
296
|
+
` payload: toRuntimePayload(input),`,
|
|
297
|
+
` options: { ...this.defaultOptions, ...options },`,
|
|
298
|
+
` }) as Promise<RuntimeResult<unknown>>;`,
|
|
299
|
+
` }`
|
|
300
|
+
].join("\n")
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const lines = [
|
|
304
|
+
`import { UxcDaemonClient, type RuntimeInvokeOptions, type RuntimeResult } from ${JSON.stringify(packageImport)};`,
|
|
305
|
+
"",
|
|
306
|
+
...typeBlocks,
|
|
307
|
+
"",
|
|
308
|
+
`export interface ${className}Options {`,
|
|
309
|
+
" client?: UxcDaemonClient;",
|
|
310
|
+
" endpoint?: string;",
|
|
311
|
+
" defaultOptions?: RuntimeInvokeOptions;",
|
|
312
|
+
"}",
|
|
313
|
+
"",
|
|
314
|
+
`export class ${className} {`,
|
|
315
|
+
" readonly client: UxcDaemonClient;",
|
|
316
|
+
" readonly endpoint: string;",
|
|
317
|
+
" readonly defaultOptions: RuntimeInvokeOptions;",
|
|
318
|
+
"",
|
|
319
|
+
` constructor(options: ${className}Options = {}) {`,
|
|
320
|
+
" this.client = options.client ?? new UxcDaemonClient();",
|
|
321
|
+
` this.endpoint = options.endpoint ?? ${JSON.stringify(schema.host.endpoint)};`,
|
|
322
|
+
" this.defaultOptions = options.defaultOptions ?? {};",
|
|
323
|
+
" }",
|
|
324
|
+
"",
|
|
325
|
+
...operationBlocks,
|
|
326
|
+
"}",
|
|
327
|
+
"",
|
|
328
|
+
"function toRuntimePayload(input: unknown): Record<string, unknown> | undefined {",
|
|
329
|
+
" if (input == null) {",
|
|
330
|
+
" return undefined;",
|
|
331
|
+
" }",
|
|
332
|
+
' if (typeof input === "object" && !Array.isArray(input)) {',
|
|
333
|
+
" return input as Record<string, unknown>;",
|
|
334
|
+
" }",
|
|
335
|
+
" return { body: input };",
|
|
336
|
+
"}"
|
|
337
|
+
];
|
|
338
|
+
if (options.includeSchemaJson) {
|
|
339
|
+
lines.push(
|
|
340
|
+
"",
|
|
341
|
+
`export const GENERATED_SCHEMA = ${JSON.stringify(schema, null, 2)} as const;`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
return `${lines.join("\n")}
|
|
345
|
+
`;
|
|
346
|
+
}
|
|
347
|
+
function defaultClassName(hostId) {
|
|
348
|
+
return `${sanitizeTypeName(hostId)}Client`;
|
|
349
|
+
}
|
|
350
|
+
function defaultMethodName(operationId) {
|
|
351
|
+
const openApiMatch = operationId.match(/^([a-z]+):\/(.*)$/i);
|
|
352
|
+
if (openApiMatch) {
|
|
353
|
+
const verb = openApiMatch[1].toLowerCase();
|
|
354
|
+
const path = openApiMatch[2].replace(/\{([^}]+)\}/g, " by $1 ").replace(/[/:_-]+/g, " ");
|
|
355
|
+
return `${verb} ${path}`;
|
|
356
|
+
}
|
|
357
|
+
return operationId.replace(/[/:._-]+/g, " ");
|
|
358
|
+
}
|
|
359
|
+
function sanitizeMethodName(raw) {
|
|
360
|
+
const tokens = raw.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
361
|
+
if (tokens.length === 0) {
|
|
362
|
+
return "invoke";
|
|
363
|
+
}
|
|
364
|
+
const [first, ...rest] = tokens;
|
|
365
|
+
const normalized = [first.toLowerCase(), ...rest.map((token) => upperFirst(token.toLowerCase()))].join(
|
|
366
|
+
""
|
|
367
|
+
);
|
|
368
|
+
return /^[a-zA-Z_$]/.test(normalized) ? normalized : `op${upperFirst(normalized)}`;
|
|
369
|
+
}
|
|
370
|
+
function sanitizeTypeName(raw) {
|
|
371
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(raw)) {
|
|
372
|
+
return raw;
|
|
373
|
+
}
|
|
374
|
+
const tokens = raw.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => upperFirst(token));
|
|
375
|
+
const candidate = tokens.join("") || "Generated";
|
|
376
|
+
return /^[A-Za-z_$]/.test(candidate) ? candidate : `T${candidate}`;
|
|
377
|
+
}
|
|
378
|
+
function upperFirst(input) {
|
|
379
|
+
if (input.length === 0) {
|
|
380
|
+
return input;
|
|
381
|
+
}
|
|
382
|
+
return `${input[0].toUpperCase()}${input.slice(1)}`;
|
|
383
|
+
}
|
|
384
|
+
function uniqueName(name, used) {
|
|
385
|
+
let candidate = name;
|
|
386
|
+
let idx = 2;
|
|
387
|
+
while (used.has(candidate)) {
|
|
388
|
+
candidate = `${name}${idx}`;
|
|
389
|
+
idx += 1;
|
|
390
|
+
}
|
|
391
|
+
used.add(candidate);
|
|
392
|
+
return candidate;
|
|
393
|
+
}
|
|
394
|
+
function hasRequiredInput(schema) {
|
|
395
|
+
if (!schema || typeof schema !== "object") {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
const required = schema.required;
|
|
399
|
+
return Array.isArray(required) && required.length > 0;
|
|
400
|
+
}
|
|
401
|
+
function selectOperationInputSchema(inputSchema) {
|
|
402
|
+
if (!inputSchema || typeof inputSchema !== "object") {
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
const obj = inputSchema;
|
|
406
|
+
if (obj.kind === "grpc_message" && typeof obj.schema === "object") {
|
|
407
|
+
return obj.schema;
|
|
408
|
+
}
|
|
409
|
+
if (obj.kind === "openrpc_method" && Array.isArray(obj.params)) {
|
|
410
|
+
const properties = {};
|
|
411
|
+
const required = [];
|
|
412
|
+
for (const raw of obj.params) {
|
|
413
|
+
if (!raw || typeof raw !== "object") {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const param = raw;
|
|
417
|
+
const name = typeof param.name === "string" ? param.name : void 0;
|
|
418
|
+
if (!name) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const schema = typeof param.schema === "object" ? param.schema : {};
|
|
422
|
+
properties[name] = schema;
|
|
423
|
+
if (param.required === true) {
|
|
424
|
+
required.push(name);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return { type: "object", properties, required };
|
|
428
|
+
}
|
|
429
|
+
if (obj.kind === "openapi_request_body" && obj.content && typeof obj.content === "object") {
|
|
430
|
+
const content = obj.content;
|
|
431
|
+
const prioritized = [
|
|
432
|
+
"application/json",
|
|
433
|
+
"application/x-www-form-urlencoded",
|
|
434
|
+
"multipart/form-data"
|
|
435
|
+
];
|
|
436
|
+
for (const mime of prioritized) {
|
|
437
|
+
const entry = content[mime];
|
|
438
|
+
if (entry && typeof entry === "object" && entry.schema) {
|
|
439
|
+
return entry.schema;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
for (const entry of Object.values(content)) {
|
|
443
|
+
if (entry && typeof entry === "object" && entry.schema) {
|
|
444
|
+
return entry.schema;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (typeof obj.schema === "object" && obj.type == null) {
|
|
449
|
+
return obj.schema;
|
|
450
|
+
}
|
|
451
|
+
return inputSchema;
|
|
452
|
+
}
|
|
453
|
+
function renderTsTypeFromSchema(schema, depth) {
|
|
454
|
+
if (!schema || typeof schema !== "object") {
|
|
455
|
+
return "Record<string, unknown>";
|
|
456
|
+
}
|
|
457
|
+
const obj = schema;
|
|
458
|
+
const typeInfo = resolveSchemaType(obj.type);
|
|
459
|
+
const enumValues = asPrimitiveArray(obj.enum);
|
|
460
|
+
if (enumValues && enumValues.length > 0) {
|
|
461
|
+
return withNullable(
|
|
462
|
+
enumValues.map((value) => JSON.stringify(value)).join(" | "),
|
|
463
|
+
typeInfo.nullable
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
if (Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
|
467
|
+
return withNullable(
|
|
468
|
+
obj.oneOf.map((item) => renderTsTypeFromSchema(item, depth + 1)).join(" | "),
|
|
469
|
+
typeInfo.nullable
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
if (Array.isArray(obj.anyOf) && obj.anyOf.length > 0) {
|
|
473
|
+
return withNullable(
|
|
474
|
+
obj.anyOf.map((item) => renderTsTypeFromSchema(item, depth + 1)).join(" | "),
|
|
475
|
+
typeInfo.nullable
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
const schemaType = typeInfo.base;
|
|
479
|
+
const openApiNullable = obj.nullable === true;
|
|
480
|
+
switch (schemaType) {
|
|
481
|
+
case "string":
|
|
482
|
+
return withNullable("string", typeInfo.nullable || openApiNullable);
|
|
483
|
+
case "integer":
|
|
484
|
+
case "number":
|
|
485
|
+
return withNullable("number", typeInfo.nullable || openApiNullable);
|
|
486
|
+
case "boolean":
|
|
487
|
+
return withNullable("boolean", typeInfo.nullable || openApiNullable);
|
|
488
|
+
case "null":
|
|
489
|
+
return "null";
|
|
490
|
+
case "array": {
|
|
491
|
+
const itemType = renderTsTypeFromSchema(obj.items, depth + 1);
|
|
492
|
+
return withNullable(`Array<${itemType}>`, typeInfo.nullable || openApiNullable);
|
|
493
|
+
}
|
|
494
|
+
case "object": {
|
|
495
|
+
const properties = obj.properties;
|
|
496
|
+
if (!properties || typeof properties !== "object") {
|
|
497
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
498
|
+
}
|
|
499
|
+
if (depth > 4) {
|
|
500
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
501
|
+
}
|
|
502
|
+
const required = new Set(
|
|
503
|
+
Array.isArray(obj.required) ? obj.required.filter((item) => typeof item === "string") : []
|
|
504
|
+
);
|
|
505
|
+
const fields = Object.entries(properties).map(([name, value]) => {
|
|
506
|
+
const optional = required.has(name) ? "" : "?";
|
|
507
|
+
const key = safePropertyName(name);
|
|
508
|
+
const valueType = renderTsTypeFromSchema(value, depth + 1);
|
|
509
|
+
return `${key}${optional}: ${valueType}`;
|
|
510
|
+
});
|
|
511
|
+
if (fields.length === 0) {
|
|
512
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
513
|
+
}
|
|
514
|
+
return withNullable(`{ ${fields.join("; ")} }`, typeInfo.nullable || openApiNullable);
|
|
515
|
+
}
|
|
516
|
+
default:
|
|
517
|
+
return withNullable("unknown", typeInfo.nullable || openApiNullable);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function resolveSchemaType(typeValue) {
|
|
521
|
+
if (typeof typeValue === "string") {
|
|
522
|
+
return {
|
|
523
|
+
base: typeValue,
|
|
524
|
+
nullable: typeValue === "null"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (Array.isArray(typeValue)) {
|
|
528
|
+
const nullable = typeValue.some((entry) => entry === "null");
|
|
529
|
+
const nonNull = typeValue.find(
|
|
530
|
+
(entry) => typeof entry === "string" && entry !== "null"
|
|
531
|
+
);
|
|
532
|
+
return {
|
|
533
|
+
base: nonNull,
|
|
534
|
+
nullable
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
base: void 0,
|
|
539
|
+
nullable: false
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
function asPrimitiveArray(value) {
|
|
543
|
+
if (!Array.isArray(value)) {
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
const allPrimitive = value.every(
|
|
547
|
+
(item) => item == null || typeof item === "string" || typeof item === "number" || typeof item === "boolean"
|
|
548
|
+
);
|
|
549
|
+
return allPrimitive ? value : void 0;
|
|
550
|
+
}
|
|
551
|
+
function safePropertyName(name) {
|
|
552
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
553
|
+
}
|
|
554
|
+
function withNullable(type, nullable) {
|
|
555
|
+
if (!nullable || type.includes("null")) {
|
|
556
|
+
return type;
|
|
557
|
+
}
|
|
558
|
+
return `${type} | null`;
|
|
559
|
+
}
|
|
560
|
+
function assertCodegenHostSchema(value) {
|
|
561
|
+
const obj = asRecord(value, "codegen schema");
|
|
562
|
+
assertString(obj.version, "codegen.version");
|
|
563
|
+
assertNumber(obj.generated_at_unix, "codegen.generated_at_unix");
|
|
564
|
+
const host = asRecord(obj.host, "codegen.host");
|
|
565
|
+
assertString(host.id, "codegen.host.id");
|
|
566
|
+
assertString(host.endpoint, "codegen.host.endpoint");
|
|
567
|
+
assertString(host.protocol, "codegen.host.protocol");
|
|
568
|
+
const runtime = asRecord(obj.runtime, "codegen.runtime");
|
|
569
|
+
if (!("invoke_options_schema" in runtime)) {
|
|
570
|
+
throw new Error("Invalid codegen schema: runtime.invoke_options_schema is required");
|
|
571
|
+
}
|
|
572
|
+
if (!("result_meta_schema" in runtime)) {
|
|
573
|
+
throw new Error("Invalid codegen schema: runtime.result_meta_schema is required");
|
|
574
|
+
}
|
|
575
|
+
if (!("artifact_meta_schema" in runtime)) {
|
|
576
|
+
throw new Error("Invalid codegen schema: runtime.artifact_meta_schema is required");
|
|
577
|
+
}
|
|
578
|
+
if (!("lifecycle_contract" in runtime)) {
|
|
579
|
+
throw new Error("Invalid codegen schema: runtime.lifecycle_contract is required");
|
|
580
|
+
}
|
|
581
|
+
if (!("artifact_contract" in runtime)) {
|
|
582
|
+
throw new Error("Invalid codegen schema: runtime.artifact_contract is required");
|
|
583
|
+
}
|
|
584
|
+
if (!Array.isArray(obj.operations)) {
|
|
585
|
+
throw new Error("Invalid codegen schema: operations must be an array");
|
|
586
|
+
}
|
|
587
|
+
for (const [index, operationValue] of obj.operations.entries()) {
|
|
588
|
+
const operation = asRecord(operationValue, `codegen.operations[${index}]`);
|
|
589
|
+
assertString(operation.id, `codegen.operations[${index}].id`);
|
|
590
|
+
assertString(operation.display_name, `codegen.operations[${index}].display_name`);
|
|
591
|
+
assertString(operation.kind, `codegen.operations[${index}].kind`);
|
|
592
|
+
assertString(operation.result_kind, `codegen.operations[${index}].result_kind`);
|
|
593
|
+
assertBoolean(operation.execute, `codegen.operations[${index}].execute`);
|
|
594
|
+
assertBoolean(operation.help_only, `codegen.operations[${index}].help_only`);
|
|
595
|
+
assertBoolean(operation.subscribable, `codegen.operations[${index}].subscribable`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function asRecord(value, label) {
|
|
599
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
600
|
+
throw new Error(`Invalid ${label}: expected object`);
|
|
601
|
+
}
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
function assertString(value, label) {
|
|
605
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
606
|
+
throw new Error(`Invalid ${label}: expected non-empty string`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function assertNumber(value, label) {
|
|
610
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
611
|
+
throw new Error(`Invalid ${label}: expected number`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function assertBoolean(value, label) {
|
|
615
|
+
if (typeof value !== "boolean") {
|
|
616
|
+
throw new Error(`Invalid ${label}: expected boolean`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
232
619
|
function normalizeOptions(options) {
|
|
233
620
|
return {
|
|
234
621
|
inject_env: [],
|
|
@@ -279,5 +666,6 @@ function tryParseFrame(buffer) {
|
|
|
279
666
|
// Annotate the CommonJS export names for ESM import in node:
|
|
280
667
|
0 && (module.exports = {
|
|
281
668
|
DaemonRpcError,
|
|
282
|
-
UxcDaemonClient
|
|
669
|
+
UxcDaemonClient,
|
|
670
|
+
generateTypeScriptClient
|
|
283
671
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -6,9 +6,13 @@ interface RuntimeInvokeOptions {
|
|
|
6
6
|
refresh_schema?: boolean;
|
|
7
7
|
schema_url?: string;
|
|
8
8
|
link_name?: string;
|
|
9
|
+
link_skill?: string;
|
|
10
|
+
link_skill_doc?: string;
|
|
11
|
+
link_skill_path?: string;
|
|
9
12
|
schema_mapping_file?: string;
|
|
10
13
|
daemon_exclusive?: string[];
|
|
11
14
|
daemon_idle_ttl?: number;
|
|
15
|
+
request_headers?: Record<string, string>;
|
|
12
16
|
}
|
|
13
17
|
interface RuntimeInvokeResponse {
|
|
14
18
|
protocol: string;
|
|
@@ -17,8 +21,59 @@ interface RuntimeInvokeResponse {
|
|
|
17
21
|
operation?: string | null;
|
|
18
22
|
data: unknown;
|
|
19
23
|
duration_ms?: number | null;
|
|
20
|
-
meta:
|
|
24
|
+
meta: RuntimeMeta;
|
|
21
25
|
}
|
|
26
|
+
interface RuntimeMeta {
|
|
27
|
+
schema_involved?: boolean | null;
|
|
28
|
+
cache_source?: string | null;
|
|
29
|
+
cache_age_ms?: number | null;
|
|
30
|
+
cache_stale?: boolean | null;
|
|
31
|
+
cache_fallback?: boolean | null;
|
|
32
|
+
daemon_session_reused?: boolean | null;
|
|
33
|
+
response_status_code?: number | null;
|
|
34
|
+
response_headers?: Record<string, string> | null;
|
|
35
|
+
artifact_truncated?: boolean | null;
|
|
36
|
+
artifact_kind?: string | null;
|
|
37
|
+
artifact_bytes?: number | null;
|
|
38
|
+
artifact_path?: string | null;
|
|
39
|
+
artifact_ref?: string | null;
|
|
40
|
+
artifact_sha256?: string | null;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
interface CodegenHostSchemaV1 {
|
|
44
|
+
version: "v1" | string;
|
|
45
|
+
generated_at_unix: number;
|
|
46
|
+
host: {
|
|
47
|
+
id: string;
|
|
48
|
+
endpoint: string;
|
|
49
|
+
protocol: string;
|
|
50
|
+
link_name?: string | null;
|
|
51
|
+
};
|
|
52
|
+
runtime: {
|
|
53
|
+
invoke_options_schema: unknown;
|
|
54
|
+
result_meta_schema: unknown;
|
|
55
|
+
artifact_meta_schema: unknown;
|
|
56
|
+
lifecycle_contract: unknown;
|
|
57
|
+
artifact_contract: unknown;
|
|
58
|
+
};
|
|
59
|
+
operations: CodegenOperationV1[];
|
|
60
|
+
}
|
|
61
|
+
interface CodegenOperationV1 {
|
|
62
|
+
id: string;
|
|
63
|
+
display_name: string;
|
|
64
|
+
description?: string | null;
|
|
65
|
+
kind: string;
|
|
66
|
+
input_schema?: unknown;
|
|
67
|
+
output_schema?: unknown;
|
|
68
|
+
result_kind: string;
|
|
69
|
+
execute: boolean;
|
|
70
|
+
help_only: boolean;
|
|
71
|
+
subscribable: boolean;
|
|
72
|
+
}
|
|
73
|
+
type RuntimeResult<TData = unknown, TMeta = Record<string, unknown>> = Omit<RuntimeInvokeResponse, "data" | "meta"> & {
|
|
74
|
+
data: TData;
|
|
75
|
+
meta: TMeta;
|
|
76
|
+
};
|
|
22
77
|
interface SubscriptionEventEnvelope {
|
|
23
78
|
version: string;
|
|
24
79
|
job_id: string;
|
|
@@ -92,18 +147,48 @@ interface UxcDaemonClientOptions {
|
|
|
92
147
|
requestTimeoutMs?: number;
|
|
93
148
|
env?: NodeJS.ProcessEnv;
|
|
94
149
|
}
|
|
150
|
+
interface GenerateTypeScriptClientOptions {
|
|
151
|
+
className?: string;
|
|
152
|
+
packageImport?: string;
|
|
153
|
+
includeSchemaJson?: boolean;
|
|
154
|
+
}
|
|
95
155
|
interface SubscribeStartArgs {
|
|
96
156
|
endpoint: string;
|
|
97
157
|
resourceUri?: string;
|
|
98
158
|
operationId?: string;
|
|
99
159
|
args?: Record<string, unknown>;
|
|
100
160
|
mode?: "stream" | "poll";
|
|
161
|
+
pollConfig?: PollSubscriptionConfig;
|
|
101
162
|
options?: RuntimeInvokeOptions;
|
|
102
163
|
sink?: `file:${string}` | "memory:";
|
|
103
164
|
ephemeral?: boolean;
|
|
104
165
|
readResource?: boolean;
|
|
105
166
|
transportHint?: "websocket" | "discord_gateway" | "slack_socket_mode" | "feishu_long_connection";
|
|
106
167
|
}
|
|
168
|
+
interface PollSubscriptionConfig {
|
|
169
|
+
interval_secs: number;
|
|
170
|
+
extract_items_pointer: string;
|
|
171
|
+
missing_extract_items_pointer_as_empty?: boolean;
|
|
172
|
+
request_cursor_arg?: string;
|
|
173
|
+
response_cursor_pointer?: string;
|
|
174
|
+
cursor_from_item_pointer?: string;
|
|
175
|
+
cursor_transform?: "increment";
|
|
176
|
+
checkpoint_strategy: {
|
|
177
|
+
type: "cursor_only";
|
|
178
|
+
} | {
|
|
179
|
+
type: "item_key";
|
|
180
|
+
item_key_pointer: string;
|
|
181
|
+
seen_window?: number;
|
|
182
|
+
} | {
|
|
183
|
+
type: "watermark";
|
|
184
|
+
item_watermark_pointer: string;
|
|
185
|
+
item_tiebreaker_pointer?: string;
|
|
186
|
+
seen_window?: number;
|
|
187
|
+
} | {
|
|
188
|
+
type: "content_hash";
|
|
189
|
+
seen_window?: number;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
107
192
|
declare class DaemonRpcError extends Error {
|
|
108
193
|
readonly code: number;
|
|
109
194
|
readonly method: string;
|
|
@@ -123,9 +208,18 @@ declare class UxcDaemonClient {
|
|
|
123
208
|
call(args: {
|
|
124
209
|
endpoint: string;
|
|
125
210
|
operation: string;
|
|
126
|
-
payload?: Record<string, unknown
|
|
211
|
+
payload?: Record<string, unknown> | undefined;
|
|
127
212
|
options?: RuntimeInvokeOptions;
|
|
128
213
|
}): Promise<RuntimeInvokeResponse>;
|
|
214
|
+
codegenSchema(args: {
|
|
215
|
+
endpoint: string;
|
|
216
|
+
options?: RuntimeInvokeOptions;
|
|
217
|
+
}): Promise<CodegenHostSchemaV1>;
|
|
218
|
+
generateTypeScriptClient(args: {
|
|
219
|
+
endpoint: string;
|
|
220
|
+
options?: RuntimeInvokeOptions;
|
|
221
|
+
emitter?: GenerateTypeScriptClientOptions;
|
|
222
|
+
}): Promise<string>;
|
|
129
223
|
subscribeStart(args: SubscribeStartArgs): Promise<SubscribeStartResponse>;
|
|
130
224
|
subscribeList(): Promise<SubscriptionJobView[]>;
|
|
131
225
|
subscribeStatus(jobId: string): Promise<SubscriptionJobView>;
|
|
@@ -146,5 +240,6 @@ declare class UxcDaemonClient {
|
|
|
146
240
|
private ensureDaemon;
|
|
147
241
|
private requestOnce;
|
|
148
242
|
}
|
|
243
|
+
declare function generateTypeScriptClient(schema: CodegenHostSchemaV1, options?: GenerateTypeScriptClientOptions): string;
|
|
149
244
|
|
|
150
|
-
export { DaemonRpcError, type DaemonStatus, type RuntimeInvokeOptions, type RuntimeInvokeResponse, type SubscribeStartArgs, type SubscribeStartResponse, type SubscribeStopResponse, type SubscriptionEventEnvelope, type SubscriptionEventsResponse, type SubscriptionJobView, UxcDaemonClient, type UxcDaemonClientOptions };
|
|
245
|
+
export { type CodegenHostSchemaV1, type CodegenOperationV1, DaemonRpcError, type DaemonStatus, type GenerateTypeScriptClientOptions, type PollSubscriptionConfig, type RuntimeInvokeOptions, type RuntimeInvokeResponse, type RuntimeMeta, type RuntimeResult, type SubscribeStartArgs, type SubscribeStartResponse, type SubscribeStopResponse, type SubscriptionEventEnvelope, type SubscriptionEventsResponse, type SubscriptionJobView, UxcDaemonClient, type UxcDaemonClientOptions, generateTypeScriptClient };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,9 +6,13 @@ interface RuntimeInvokeOptions {
|
|
|
6
6
|
refresh_schema?: boolean;
|
|
7
7
|
schema_url?: string;
|
|
8
8
|
link_name?: string;
|
|
9
|
+
link_skill?: string;
|
|
10
|
+
link_skill_doc?: string;
|
|
11
|
+
link_skill_path?: string;
|
|
9
12
|
schema_mapping_file?: string;
|
|
10
13
|
daemon_exclusive?: string[];
|
|
11
14
|
daemon_idle_ttl?: number;
|
|
15
|
+
request_headers?: Record<string, string>;
|
|
12
16
|
}
|
|
13
17
|
interface RuntimeInvokeResponse {
|
|
14
18
|
protocol: string;
|
|
@@ -17,8 +21,59 @@ interface RuntimeInvokeResponse {
|
|
|
17
21
|
operation?: string | null;
|
|
18
22
|
data: unknown;
|
|
19
23
|
duration_ms?: number | null;
|
|
20
|
-
meta:
|
|
24
|
+
meta: RuntimeMeta;
|
|
21
25
|
}
|
|
26
|
+
interface RuntimeMeta {
|
|
27
|
+
schema_involved?: boolean | null;
|
|
28
|
+
cache_source?: string | null;
|
|
29
|
+
cache_age_ms?: number | null;
|
|
30
|
+
cache_stale?: boolean | null;
|
|
31
|
+
cache_fallback?: boolean | null;
|
|
32
|
+
daemon_session_reused?: boolean | null;
|
|
33
|
+
response_status_code?: number | null;
|
|
34
|
+
response_headers?: Record<string, string> | null;
|
|
35
|
+
artifact_truncated?: boolean | null;
|
|
36
|
+
artifact_kind?: string | null;
|
|
37
|
+
artifact_bytes?: number | null;
|
|
38
|
+
artifact_path?: string | null;
|
|
39
|
+
artifact_ref?: string | null;
|
|
40
|
+
artifact_sha256?: string | null;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
}
|
|
43
|
+
interface CodegenHostSchemaV1 {
|
|
44
|
+
version: "v1" | string;
|
|
45
|
+
generated_at_unix: number;
|
|
46
|
+
host: {
|
|
47
|
+
id: string;
|
|
48
|
+
endpoint: string;
|
|
49
|
+
protocol: string;
|
|
50
|
+
link_name?: string | null;
|
|
51
|
+
};
|
|
52
|
+
runtime: {
|
|
53
|
+
invoke_options_schema: unknown;
|
|
54
|
+
result_meta_schema: unknown;
|
|
55
|
+
artifact_meta_schema: unknown;
|
|
56
|
+
lifecycle_contract: unknown;
|
|
57
|
+
artifact_contract: unknown;
|
|
58
|
+
};
|
|
59
|
+
operations: CodegenOperationV1[];
|
|
60
|
+
}
|
|
61
|
+
interface CodegenOperationV1 {
|
|
62
|
+
id: string;
|
|
63
|
+
display_name: string;
|
|
64
|
+
description?: string | null;
|
|
65
|
+
kind: string;
|
|
66
|
+
input_schema?: unknown;
|
|
67
|
+
output_schema?: unknown;
|
|
68
|
+
result_kind: string;
|
|
69
|
+
execute: boolean;
|
|
70
|
+
help_only: boolean;
|
|
71
|
+
subscribable: boolean;
|
|
72
|
+
}
|
|
73
|
+
type RuntimeResult<TData = unknown, TMeta = Record<string, unknown>> = Omit<RuntimeInvokeResponse, "data" | "meta"> & {
|
|
74
|
+
data: TData;
|
|
75
|
+
meta: TMeta;
|
|
76
|
+
};
|
|
22
77
|
interface SubscriptionEventEnvelope {
|
|
23
78
|
version: string;
|
|
24
79
|
job_id: string;
|
|
@@ -92,18 +147,48 @@ interface UxcDaemonClientOptions {
|
|
|
92
147
|
requestTimeoutMs?: number;
|
|
93
148
|
env?: NodeJS.ProcessEnv;
|
|
94
149
|
}
|
|
150
|
+
interface GenerateTypeScriptClientOptions {
|
|
151
|
+
className?: string;
|
|
152
|
+
packageImport?: string;
|
|
153
|
+
includeSchemaJson?: boolean;
|
|
154
|
+
}
|
|
95
155
|
interface SubscribeStartArgs {
|
|
96
156
|
endpoint: string;
|
|
97
157
|
resourceUri?: string;
|
|
98
158
|
operationId?: string;
|
|
99
159
|
args?: Record<string, unknown>;
|
|
100
160
|
mode?: "stream" | "poll";
|
|
161
|
+
pollConfig?: PollSubscriptionConfig;
|
|
101
162
|
options?: RuntimeInvokeOptions;
|
|
102
163
|
sink?: `file:${string}` | "memory:";
|
|
103
164
|
ephemeral?: boolean;
|
|
104
165
|
readResource?: boolean;
|
|
105
166
|
transportHint?: "websocket" | "discord_gateway" | "slack_socket_mode" | "feishu_long_connection";
|
|
106
167
|
}
|
|
168
|
+
interface PollSubscriptionConfig {
|
|
169
|
+
interval_secs: number;
|
|
170
|
+
extract_items_pointer: string;
|
|
171
|
+
missing_extract_items_pointer_as_empty?: boolean;
|
|
172
|
+
request_cursor_arg?: string;
|
|
173
|
+
response_cursor_pointer?: string;
|
|
174
|
+
cursor_from_item_pointer?: string;
|
|
175
|
+
cursor_transform?: "increment";
|
|
176
|
+
checkpoint_strategy: {
|
|
177
|
+
type: "cursor_only";
|
|
178
|
+
} | {
|
|
179
|
+
type: "item_key";
|
|
180
|
+
item_key_pointer: string;
|
|
181
|
+
seen_window?: number;
|
|
182
|
+
} | {
|
|
183
|
+
type: "watermark";
|
|
184
|
+
item_watermark_pointer: string;
|
|
185
|
+
item_tiebreaker_pointer?: string;
|
|
186
|
+
seen_window?: number;
|
|
187
|
+
} | {
|
|
188
|
+
type: "content_hash";
|
|
189
|
+
seen_window?: number;
|
|
190
|
+
};
|
|
191
|
+
}
|
|
107
192
|
declare class DaemonRpcError extends Error {
|
|
108
193
|
readonly code: number;
|
|
109
194
|
readonly method: string;
|
|
@@ -123,9 +208,18 @@ declare class UxcDaemonClient {
|
|
|
123
208
|
call(args: {
|
|
124
209
|
endpoint: string;
|
|
125
210
|
operation: string;
|
|
126
|
-
payload?: Record<string, unknown
|
|
211
|
+
payload?: Record<string, unknown> | undefined;
|
|
127
212
|
options?: RuntimeInvokeOptions;
|
|
128
213
|
}): Promise<RuntimeInvokeResponse>;
|
|
214
|
+
codegenSchema(args: {
|
|
215
|
+
endpoint: string;
|
|
216
|
+
options?: RuntimeInvokeOptions;
|
|
217
|
+
}): Promise<CodegenHostSchemaV1>;
|
|
218
|
+
generateTypeScriptClient(args: {
|
|
219
|
+
endpoint: string;
|
|
220
|
+
options?: RuntimeInvokeOptions;
|
|
221
|
+
emitter?: GenerateTypeScriptClientOptions;
|
|
222
|
+
}): Promise<string>;
|
|
129
223
|
subscribeStart(args: SubscribeStartArgs): Promise<SubscribeStartResponse>;
|
|
130
224
|
subscribeList(): Promise<SubscriptionJobView[]>;
|
|
131
225
|
subscribeStatus(jobId: string): Promise<SubscriptionJobView>;
|
|
@@ -146,5 +240,6 @@ declare class UxcDaemonClient {
|
|
|
146
240
|
private ensureDaemon;
|
|
147
241
|
private requestOnce;
|
|
148
242
|
}
|
|
243
|
+
declare function generateTypeScriptClient(schema: CodegenHostSchemaV1, options?: GenerateTypeScriptClientOptions): string;
|
|
149
244
|
|
|
150
|
-
export { DaemonRpcError, type DaemonStatus, type RuntimeInvokeOptions, type RuntimeInvokeResponse, type SubscribeStartArgs, type SubscribeStartResponse, type SubscribeStopResponse, type SubscriptionEventEnvelope, type SubscriptionEventsResponse, type SubscriptionJobView, UxcDaemonClient, type UxcDaemonClientOptions };
|
|
245
|
+
export { type CodegenHostSchemaV1, type CodegenOperationV1, DaemonRpcError, type DaemonStatus, type GenerateTypeScriptClientOptions, type PollSubscriptionConfig, type RuntimeInvokeOptions, type RuntimeInvokeResponse, type RuntimeMeta, type RuntimeResult, type SubscribeStartArgs, type SubscribeStartResponse, type SubscribeStopResponse, type SubscriptionEventEnvelope, type SubscriptionEventsResponse, type SubscriptionJobView, UxcDaemonClient, type UxcDaemonClientOptions, generateTypeScriptClient };
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,38 @@ var UxcDaemonClient = class {
|
|
|
47
47
|
options: normalizeOptions(args.options)
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
async codegenSchema(args) {
|
|
51
|
+
const response = await this.request("runtime.invoke", {
|
|
52
|
+
request_id: requestId("codegen"),
|
|
53
|
+
endpoint: args.endpoint,
|
|
54
|
+
action: "codegen_schema",
|
|
55
|
+
operation_id: null,
|
|
56
|
+
args: null,
|
|
57
|
+
options: normalizeOptions(args.options)
|
|
58
|
+
});
|
|
59
|
+
if (response.kind !== "codegen_host_schema") {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Unexpected codegen response kind '${response.kind}' (expected codegen_host_schema)`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
assertCodegenHostSchema(response.data);
|
|
65
|
+
return response.data;
|
|
66
|
+
}
|
|
67
|
+
async generateTypeScriptClient(args) {
|
|
68
|
+
const schema = await this.codegenSchema({
|
|
69
|
+
endpoint: args.endpoint,
|
|
70
|
+
options: args.options
|
|
71
|
+
});
|
|
72
|
+
return generateTypeScriptClient(schema, args.emitter);
|
|
73
|
+
}
|
|
50
74
|
async subscribeStart(args) {
|
|
75
|
+
const mode = args.mode ?? (args.pollConfig ? "poll" : "stream");
|
|
76
|
+
if (mode === "poll" && !args.pollConfig) {
|
|
77
|
+
throw new Error("pollConfig is required when mode is 'poll'");
|
|
78
|
+
}
|
|
79
|
+
if (mode !== "poll" && args.pollConfig) {
|
|
80
|
+
throw new Error("pollConfig is only valid when mode is 'poll'");
|
|
81
|
+
}
|
|
51
82
|
return this.request("subscription.start", {
|
|
52
83
|
request_id: requestId("subscribe"),
|
|
53
84
|
endpoint: args.endpoint,
|
|
@@ -59,8 +90,8 @@ var UxcDaemonClient = class {
|
|
|
59
90
|
transport_hint: args.transportHint ?? null,
|
|
60
91
|
subprotocols: [],
|
|
61
92
|
initial_text_frames: [],
|
|
62
|
-
mode
|
|
63
|
-
poll_config: null,
|
|
93
|
+
mode,
|
|
94
|
+
poll_config: args.pollConfig ?? null,
|
|
64
95
|
ephemeral: args.ephemeral ?? (args.sink ?? "memory:") === "memory:",
|
|
65
96
|
options: normalizeOptions(args.options)
|
|
66
97
|
});
|
|
@@ -194,6 +225,361 @@ var UxcDaemonClient = class {
|
|
|
194
225
|
return response;
|
|
195
226
|
}
|
|
196
227
|
};
|
|
228
|
+
function generateTypeScriptClient(schema, options = {}) {
|
|
229
|
+
const packageImport = options.packageImport ?? "@holon-run/uxc-daemon-client";
|
|
230
|
+
const className = sanitizeTypeName(options.className ?? defaultClassName(schema.host.id));
|
|
231
|
+
const methodNames = /* @__PURE__ */ new Set();
|
|
232
|
+
const typeNames = /* @__PURE__ */ new Set();
|
|
233
|
+
const operationBlocks = [];
|
|
234
|
+
const typeBlocks = [];
|
|
235
|
+
for (const operation of schema.operations) {
|
|
236
|
+
if (!operation.execute || operation.help_only || operation.subscribable) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const methodName = uniqueName(
|
|
240
|
+
sanitizeMethodName(defaultMethodName(operation.id)),
|
|
241
|
+
methodNames
|
|
242
|
+
);
|
|
243
|
+
const typeName = uniqueName(
|
|
244
|
+
sanitizeTypeName(`${upperFirst(methodName)}Input`),
|
|
245
|
+
typeNames
|
|
246
|
+
);
|
|
247
|
+
const selectedInputSchema = selectOperationInputSchema(operation.input_schema);
|
|
248
|
+
const inputType = selectedInputSchema ? renderTsTypeFromSchema(selectedInputSchema, 0) : "Record<string, unknown>";
|
|
249
|
+
const inputRequired = selectedInputSchema ? hasRequiredInput(selectedInputSchema) : false;
|
|
250
|
+
typeBlocks.push(`export type ${typeName} = ${inputType};`);
|
|
251
|
+
operationBlocks.push(
|
|
252
|
+
[
|
|
253
|
+
` async ${methodName}(`,
|
|
254
|
+
inputRequired ? ` input: ${typeName},` : ` input?: ${typeName},`,
|
|
255
|
+
` options: RuntimeInvokeOptions = {},`,
|
|
256
|
+
` ): Promise<RuntimeResult<unknown>> {`,
|
|
257
|
+
` return this.client.call({`,
|
|
258
|
+
` endpoint: this.endpoint,`,
|
|
259
|
+
` operation: ${JSON.stringify(operation.id)},`,
|
|
260
|
+
` payload: toRuntimePayload(input),`,
|
|
261
|
+
` options: { ...this.defaultOptions, ...options },`,
|
|
262
|
+
` }) as Promise<RuntimeResult<unknown>>;`,
|
|
263
|
+
` }`
|
|
264
|
+
].join("\n")
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const lines = [
|
|
268
|
+
`import { UxcDaemonClient, type RuntimeInvokeOptions, type RuntimeResult } from ${JSON.stringify(packageImport)};`,
|
|
269
|
+
"",
|
|
270
|
+
...typeBlocks,
|
|
271
|
+
"",
|
|
272
|
+
`export interface ${className}Options {`,
|
|
273
|
+
" client?: UxcDaemonClient;",
|
|
274
|
+
" endpoint?: string;",
|
|
275
|
+
" defaultOptions?: RuntimeInvokeOptions;",
|
|
276
|
+
"}",
|
|
277
|
+
"",
|
|
278
|
+
`export class ${className} {`,
|
|
279
|
+
" readonly client: UxcDaemonClient;",
|
|
280
|
+
" readonly endpoint: string;",
|
|
281
|
+
" readonly defaultOptions: RuntimeInvokeOptions;",
|
|
282
|
+
"",
|
|
283
|
+
` constructor(options: ${className}Options = {}) {`,
|
|
284
|
+
" this.client = options.client ?? new UxcDaemonClient();",
|
|
285
|
+
` this.endpoint = options.endpoint ?? ${JSON.stringify(schema.host.endpoint)};`,
|
|
286
|
+
" this.defaultOptions = options.defaultOptions ?? {};",
|
|
287
|
+
" }",
|
|
288
|
+
"",
|
|
289
|
+
...operationBlocks,
|
|
290
|
+
"}",
|
|
291
|
+
"",
|
|
292
|
+
"function toRuntimePayload(input: unknown): Record<string, unknown> | undefined {",
|
|
293
|
+
" if (input == null) {",
|
|
294
|
+
" return undefined;",
|
|
295
|
+
" }",
|
|
296
|
+
' if (typeof input === "object" && !Array.isArray(input)) {',
|
|
297
|
+
" return input as Record<string, unknown>;",
|
|
298
|
+
" }",
|
|
299
|
+
" return { body: input };",
|
|
300
|
+
"}"
|
|
301
|
+
];
|
|
302
|
+
if (options.includeSchemaJson) {
|
|
303
|
+
lines.push(
|
|
304
|
+
"",
|
|
305
|
+
`export const GENERATED_SCHEMA = ${JSON.stringify(schema, null, 2)} as const;`
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return `${lines.join("\n")}
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
function defaultClassName(hostId) {
|
|
312
|
+
return `${sanitizeTypeName(hostId)}Client`;
|
|
313
|
+
}
|
|
314
|
+
function defaultMethodName(operationId) {
|
|
315
|
+
const openApiMatch = operationId.match(/^([a-z]+):\/(.*)$/i);
|
|
316
|
+
if (openApiMatch) {
|
|
317
|
+
const verb = openApiMatch[1].toLowerCase();
|
|
318
|
+
const path = openApiMatch[2].replace(/\{([^}]+)\}/g, " by $1 ").replace(/[/:_-]+/g, " ");
|
|
319
|
+
return `${verb} ${path}`;
|
|
320
|
+
}
|
|
321
|
+
return operationId.replace(/[/:._-]+/g, " ");
|
|
322
|
+
}
|
|
323
|
+
function sanitizeMethodName(raw) {
|
|
324
|
+
const tokens = raw.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
325
|
+
if (tokens.length === 0) {
|
|
326
|
+
return "invoke";
|
|
327
|
+
}
|
|
328
|
+
const [first, ...rest] = tokens;
|
|
329
|
+
const normalized = [first.toLowerCase(), ...rest.map((token) => upperFirst(token.toLowerCase()))].join(
|
|
330
|
+
""
|
|
331
|
+
);
|
|
332
|
+
return /^[a-zA-Z_$]/.test(normalized) ? normalized : `op${upperFirst(normalized)}`;
|
|
333
|
+
}
|
|
334
|
+
function sanitizeTypeName(raw) {
|
|
335
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(raw)) {
|
|
336
|
+
return raw;
|
|
337
|
+
}
|
|
338
|
+
const tokens = raw.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean).map((token) => upperFirst(token));
|
|
339
|
+
const candidate = tokens.join("") || "Generated";
|
|
340
|
+
return /^[A-Za-z_$]/.test(candidate) ? candidate : `T${candidate}`;
|
|
341
|
+
}
|
|
342
|
+
function upperFirst(input) {
|
|
343
|
+
if (input.length === 0) {
|
|
344
|
+
return input;
|
|
345
|
+
}
|
|
346
|
+
return `${input[0].toUpperCase()}${input.slice(1)}`;
|
|
347
|
+
}
|
|
348
|
+
function uniqueName(name, used) {
|
|
349
|
+
let candidate = name;
|
|
350
|
+
let idx = 2;
|
|
351
|
+
while (used.has(candidate)) {
|
|
352
|
+
candidate = `${name}${idx}`;
|
|
353
|
+
idx += 1;
|
|
354
|
+
}
|
|
355
|
+
used.add(candidate);
|
|
356
|
+
return candidate;
|
|
357
|
+
}
|
|
358
|
+
function hasRequiredInput(schema) {
|
|
359
|
+
if (!schema || typeof schema !== "object") {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
const required = schema.required;
|
|
363
|
+
return Array.isArray(required) && required.length > 0;
|
|
364
|
+
}
|
|
365
|
+
function selectOperationInputSchema(inputSchema) {
|
|
366
|
+
if (!inputSchema || typeof inputSchema !== "object") {
|
|
367
|
+
return void 0;
|
|
368
|
+
}
|
|
369
|
+
const obj = inputSchema;
|
|
370
|
+
if (obj.kind === "grpc_message" && typeof obj.schema === "object") {
|
|
371
|
+
return obj.schema;
|
|
372
|
+
}
|
|
373
|
+
if (obj.kind === "openrpc_method" && Array.isArray(obj.params)) {
|
|
374
|
+
const properties = {};
|
|
375
|
+
const required = [];
|
|
376
|
+
for (const raw of obj.params) {
|
|
377
|
+
if (!raw || typeof raw !== "object") {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
const param = raw;
|
|
381
|
+
const name = typeof param.name === "string" ? param.name : void 0;
|
|
382
|
+
if (!name) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const schema = typeof param.schema === "object" ? param.schema : {};
|
|
386
|
+
properties[name] = schema;
|
|
387
|
+
if (param.required === true) {
|
|
388
|
+
required.push(name);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return { type: "object", properties, required };
|
|
392
|
+
}
|
|
393
|
+
if (obj.kind === "openapi_request_body" && obj.content && typeof obj.content === "object") {
|
|
394
|
+
const content = obj.content;
|
|
395
|
+
const prioritized = [
|
|
396
|
+
"application/json",
|
|
397
|
+
"application/x-www-form-urlencoded",
|
|
398
|
+
"multipart/form-data"
|
|
399
|
+
];
|
|
400
|
+
for (const mime of prioritized) {
|
|
401
|
+
const entry = content[mime];
|
|
402
|
+
if (entry && typeof entry === "object" && entry.schema) {
|
|
403
|
+
return entry.schema;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
for (const entry of Object.values(content)) {
|
|
407
|
+
if (entry && typeof entry === "object" && entry.schema) {
|
|
408
|
+
return entry.schema;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (typeof obj.schema === "object" && obj.type == null) {
|
|
413
|
+
return obj.schema;
|
|
414
|
+
}
|
|
415
|
+
return inputSchema;
|
|
416
|
+
}
|
|
417
|
+
function renderTsTypeFromSchema(schema, depth) {
|
|
418
|
+
if (!schema || typeof schema !== "object") {
|
|
419
|
+
return "Record<string, unknown>";
|
|
420
|
+
}
|
|
421
|
+
const obj = schema;
|
|
422
|
+
const typeInfo = resolveSchemaType(obj.type);
|
|
423
|
+
const enumValues = asPrimitiveArray(obj.enum);
|
|
424
|
+
if (enumValues && enumValues.length > 0) {
|
|
425
|
+
return withNullable(
|
|
426
|
+
enumValues.map((value) => JSON.stringify(value)).join(" | "),
|
|
427
|
+
typeInfo.nullable
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
if (Array.isArray(obj.oneOf) && obj.oneOf.length > 0) {
|
|
431
|
+
return withNullable(
|
|
432
|
+
obj.oneOf.map((item) => renderTsTypeFromSchema(item, depth + 1)).join(" | "),
|
|
433
|
+
typeInfo.nullable
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (Array.isArray(obj.anyOf) && obj.anyOf.length > 0) {
|
|
437
|
+
return withNullable(
|
|
438
|
+
obj.anyOf.map((item) => renderTsTypeFromSchema(item, depth + 1)).join(" | "),
|
|
439
|
+
typeInfo.nullable
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
const schemaType = typeInfo.base;
|
|
443
|
+
const openApiNullable = obj.nullable === true;
|
|
444
|
+
switch (schemaType) {
|
|
445
|
+
case "string":
|
|
446
|
+
return withNullable("string", typeInfo.nullable || openApiNullable);
|
|
447
|
+
case "integer":
|
|
448
|
+
case "number":
|
|
449
|
+
return withNullable("number", typeInfo.nullable || openApiNullable);
|
|
450
|
+
case "boolean":
|
|
451
|
+
return withNullable("boolean", typeInfo.nullable || openApiNullable);
|
|
452
|
+
case "null":
|
|
453
|
+
return "null";
|
|
454
|
+
case "array": {
|
|
455
|
+
const itemType = renderTsTypeFromSchema(obj.items, depth + 1);
|
|
456
|
+
return withNullable(`Array<${itemType}>`, typeInfo.nullable || openApiNullable);
|
|
457
|
+
}
|
|
458
|
+
case "object": {
|
|
459
|
+
const properties = obj.properties;
|
|
460
|
+
if (!properties || typeof properties !== "object") {
|
|
461
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
462
|
+
}
|
|
463
|
+
if (depth > 4) {
|
|
464
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
465
|
+
}
|
|
466
|
+
const required = new Set(
|
|
467
|
+
Array.isArray(obj.required) ? obj.required.filter((item) => typeof item === "string") : []
|
|
468
|
+
);
|
|
469
|
+
const fields = Object.entries(properties).map(([name, value]) => {
|
|
470
|
+
const optional = required.has(name) ? "" : "?";
|
|
471
|
+
const key = safePropertyName(name);
|
|
472
|
+
const valueType = renderTsTypeFromSchema(value, depth + 1);
|
|
473
|
+
return `${key}${optional}: ${valueType}`;
|
|
474
|
+
});
|
|
475
|
+
if (fields.length === 0) {
|
|
476
|
+
return withNullable("Record<string, unknown>", typeInfo.nullable || openApiNullable);
|
|
477
|
+
}
|
|
478
|
+
return withNullable(`{ ${fields.join("; ")} }`, typeInfo.nullable || openApiNullable);
|
|
479
|
+
}
|
|
480
|
+
default:
|
|
481
|
+
return withNullable("unknown", typeInfo.nullable || openApiNullable);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function resolveSchemaType(typeValue) {
|
|
485
|
+
if (typeof typeValue === "string") {
|
|
486
|
+
return {
|
|
487
|
+
base: typeValue,
|
|
488
|
+
nullable: typeValue === "null"
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (Array.isArray(typeValue)) {
|
|
492
|
+
const nullable = typeValue.some((entry) => entry === "null");
|
|
493
|
+
const nonNull = typeValue.find(
|
|
494
|
+
(entry) => typeof entry === "string" && entry !== "null"
|
|
495
|
+
);
|
|
496
|
+
return {
|
|
497
|
+
base: nonNull,
|
|
498
|
+
nullable
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
base: void 0,
|
|
503
|
+
nullable: false
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
function asPrimitiveArray(value) {
|
|
507
|
+
if (!Array.isArray(value)) {
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
const allPrimitive = value.every(
|
|
511
|
+
(item) => item == null || typeof item === "string" || typeof item === "number" || typeof item === "boolean"
|
|
512
|
+
);
|
|
513
|
+
return allPrimitive ? value : void 0;
|
|
514
|
+
}
|
|
515
|
+
function safePropertyName(name) {
|
|
516
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
517
|
+
}
|
|
518
|
+
function withNullable(type, nullable) {
|
|
519
|
+
if (!nullable || type.includes("null")) {
|
|
520
|
+
return type;
|
|
521
|
+
}
|
|
522
|
+
return `${type} | null`;
|
|
523
|
+
}
|
|
524
|
+
function assertCodegenHostSchema(value) {
|
|
525
|
+
const obj = asRecord(value, "codegen schema");
|
|
526
|
+
assertString(obj.version, "codegen.version");
|
|
527
|
+
assertNumber(obj.generated_at_unix, "codegen.generated_at_unix");
|
|
528
|
+
const host = asRecord(obj.host, "codegen.host");
|
|
529
|
+
assertString(host.id, "codegen.host.id");
|
|
530
|
+
assertString(host.endpoint, "codegen.host.endpoint");
|
|
531
|
+
assertString(host.protocol, "codegen.host.protocol");
|
|
532
|
+
const runtime = asRecord(obj.runtime, "codegen.runtime");
|
|
533
|
+
if (!("invoke_options_schema" in runtime)) {
|
|
534
|
+
throw new Error("Invalid codegen schema: runtime.invoke_options_schema is required");
|
|
535
|
+
}
|
|
536
|
+
if (!("result_meta_schema" in runtime)) {
|
|
537
|
+
throw new Error("Invalid codegen schema: runtime.result_meta_schema is required");
|
|
538
|
+
}
|
|
539
|
+
if (!("artifact_meta_schema" in runtime)) {
|
|
540
|
+
throw new Error("Invalid codegen schema: runtime.artifact_meta_schema is required");
|
|
541
|
+
}
|
|
542
|
+
if (!("lifecycle_contract" in runtime)) {
|
|
543
|
+
throw new Error("Invalid codegen schema: runtime.lifecycle_contract is required");
|
|
544
|
+
}
|
|
545
|
+
if (!("artifact_contract" in runtime)) {
|
|
546
|
+
throw new Error("Invalid codegen schema: runtime.artifact_contract is required");
|
|
547
|
+
}
|
|
548
|
+
if (!Array.isArray(obj.operations)) {
|
|
549
|
+
throw new Error("Invalid codegen schema: operations must be an array");
|
|
550
|
+
}
|
|
551
|
+
for (const [index, operationValue] of obj.operations.entries()) {
|
|
552
|
+
const operation = asRecord(operationValue, `codegen.operations[${index}]`);
|
|
553
|
+
assertString(operation.id, `codegen.operations[${index}].id`);
|
|
554
|
+
assertString(operation.display_name, `codegen.operations[${index}].display_name`);
|
|
555
|
+
assertString(operation.kind, `codegen.operations[${index}].kind`);
|
|
556
|
+
assertString(operation.result_kind, `codegen.operations[${index}].result_kind`);
|
|
557
|
+
assertBoolean(operation.execute, `codegen.operations[${index}].execute`);
|
|
558
|
+
assertBoolean(operation.help_only, `codegen.operations[${index}].help_only`);
|
|
559
|
+
assertBoolean(operation.subscribable, `codegen.operations[${index}].subscribable`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function asRecord(value, label) {
|
|
563
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
564
|
+
throw new Error(`Invalid ${label}: expected object`);
|
|
565
|
+
}
|
|
566
|
+
return value;
|
|
567
|
+
}
|
|
568
|
+
function assertString(value, label) {
|
|
569
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
570
|
+
throw new Error(`Invalid ${label}: expected non-empty string`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function assertNumber(value, label) {
|
|
574
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
575
|
+
throw new Error(`Invalid ${label}: expected number`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function assertBoolean(value, label) {
|
|
579
|
+
if (typeof value !== "boolean") {
|
|
580
|
+
throw new Error(`Invalid ${label}: expected boolean`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
197
583
|
function normalizeOptions(options) {
|
|
198
584
|
return {
|
|
199
585
|
inject_env: [],
|
|
@@ -243,5 +629,6 @@ function tryParseFrame(buffer) {
|
|
|
243
629
|
}
|
|
244
630
|
export {
|
|
245
631
|
DaemonRpcError,
|
|
246
|
-
UxcDaemonClient
|
|
632
|
+
UxcDaemonClient,
|
|
633
|
+
generateTypeScriptClient
|
|
247
634
|
};
|