@aprovan/stitchery 0.1.0-dev.ba8f277 → 0.1.0-dev.c33be64
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/.turbo/turbo-build.log +17 -17
- package/dist/cli.cjs +74 -18
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +74 -18
- package/dist/cli.js.map +1 -1
- package/dist/{index-DNQY1UAP.d.cts → index-JTHGJYLz.d.cts} +11 -1
- package/dist/{index-DNQY1UAP.d.ts → index-JTHGJYLz.d.ts} +11 -1
- package/dist/index.cjs +60 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +60 -13
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +60 -13
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +60 -13
- package/dist/server/index.js.map +1 -1
- package/package.json +4 -2
- package/src/cli.ts +16 -4
- package/src/server/services.ts +99 -35
- package/src/server/vfs-routes.ts +2 -2
package/src/server/services.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Provides unified interface for calling services and exposing metadata.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { jsonSchema, type Tool } from
|
|
11
|
+
import { jsonSchema, type Tool } from "ai";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Service backend interface - abstracts service call mechanisms
|
|
@@ -30,8 +30,10 @@ export interface ServiceToolInfo {
|
|
|
30
30
|
procedure: string;
|
|
31
31
|
/** Tool description */
|
|
32
32
|
description?: string;
|
|
33
|
-
/** Parameter schema */
|
|
33
|
+
/** Parameter schema (inputs) */
|
|
34
34
|
parameters?: Record<string, unknown>;
|
|
35
|
+
/** Response schema (outputs) */
|
|
36
|
+
outputs?: Record<string, unknown>;
|
|
35
37
|
/** TypeScript interface definition (optional, for search results) */
|
|
36
38
|
typescriptInterface?: string;
|
|
37
39
|
}
|
|
@@ -70,7 +72,7 @@ export class ServiceRegistry {
|
|
|
70
72
|
this.tools.set(name, tool);
|
|
71
73
|
|
|
72
74
|
// Parse namespace and procedure from the full name using '.' separator
|
|
73
|
-
const dotIndex = name.indexOf(
|
|
75
|
+
const dotIndex = name.indexOf(".");
|
|
74
76
|
const ns = dotIndex > 0 ? name.substring(0, dotIndex) : name;
|
|
75
77
|
const procedure = dotIndex > 0 ? name.substring(dotIndex + 1) : name;
|
|
76
78
|
|
|
@@ -97,13 +99,22 @@ export class ServiceRegistry {
|
|
|
97
99
|
this.backends.push(backend);
|
|
98
100
|
if (toolInfos) {
|
|
99
101
|
for (const info of toolInfos) {
|
|
100
|
-
|
|
102
|
+
// Generate TypeScript interface including response type if outputs schema provided
|
|
103
|
+
const infoWithInterface: ServiceToolInfo = {
|
|
104
|
+
...info,
|
|
105
|
+
typescriptInterface: this.generateTypeScriptInterfaceFromSchema(
|
|
106
|
+
info.name,
|
|
107
|
+
info.parameters,
|
|
108
|
+
info.outputs,
|
|
109
|
+
),
|
|
110
|
+
};
|
|
111
|
+
this.toolInfo.set(info.name, infoWithInterface);
|
|
101
112
|
|
|
102
113
|
// Create a callable Tool object for LLM use
|
|
103
114
|
const tool: Tool = {
|
|
104
115
|
description: info.description,
|
|
105
116
|
inputSchema: jsonSchema(
|
|
106
|
-
info.parameters ?? { type:
|
|
117
|
+
info.parameters ?? { type: "object", properties: {} },
|
|
107
118
|
),
|
|
108
119
|
execute: async (args: unknown) => {
|
|
109
120
|
return backend.call(info.namespace, info.procedure, [args]);
|
|
@@ -119,34 +130,87 @@ export class ServiceRegistry {
|
|
|
119
130
|
*/
|
|
120
131
|
private generateTypeScriptInterface(name: string, tool: Tool): string {
|
|
121
132
|
const schema = tool.inputSchema as Record<string, unknown> | undefined;
|
|
122
|
-
|
|
133
|
+
return this.generateTypeScriptInterfaceFromSchema(name, schema);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Convert JSON Schema type to TypeScript type
|
|
138
|
+
*/
|
|
139
|
+
private schemaTypeToTs(schema: Record<string, unknown>): string {
|
|
140
|
+
const type = schema.type as string | undefined;
|
|
141
|
+
const items = schema.items as Record<string, unknown> | undefined;
|
|
142
|
+
const properties = schema.properties as
|
|
143
|
+
| Record<string, Record<string, unknown>>
|
|
144
|
+
| undefined;
|
|
145
|
+
const required = (schema.required ?? []) as string[];
|
|
146
|
+
|
|
147
|
+
if (type === "number" || type === "integer") return "number";
|
|
148
|
+
if (type === "boolean") return "boolean";
|
|
149
|
+
if (type === "null") return "null";
|
|
150
|
+
if (type === "array") {
|
|
151
|
+
if (items) {
|
|
152
|
+
return `${this.schemaTypeToTs(items)}[]`;
|
|
153
|
+
}
|
|
154
|
+
return "unknown[]";
|
|
155
|
+
}
|
|
156
|
+
if (type === "object" && properties) {
|
|
157
|
+
const props = Object.entries(properties)
|
|
158
|
+
.map(([key, val]) => {
|
|
159
|
+
const optional = !required.includes(key) ? "?" : "";
|
|
160
|
+
const propType = this.schemaTypeToTs(val);
|
|
161
|
+
return `${key}${optional}: ${propType}`;
|
|
162
|
+
})
|
|
163
|
+
.join("; ");
|
|
164
|
+
return `{ ${props} }`;
|
|
165
|
+
}
|
|
166
|
+
if (type === "object") return "Record<string, unknown>";
|
|
167
|
+
return "string";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Generate TypeScript interface from JSON Schema (supports both inputs and outputs)
|
|
172
|
+
*/
|
|
173
|
+
private generateTypeScriptInterfaceFromSchema(
|
|
174
|
+
name: string,
|
|
175
|
+
inputSchema?: Record<string, unknown>,
|
|
176
|
+
outputSchema?: Record<string, unknown>,
|
|
177
|
+
): string {
|
|
178
|
+
const safeName = name.replace(/[^a-zA-Z0-9]/g, "_");
|
|
179
|
+
|
|
180
|
+
// Generate Args interface
|
|
181
|
+
const inputProps = (inputSchema?.properties ?? {}) as Record<
|
|
123
182
|
string,
|
|
124
183
|
{ type?: string; description?: string }
|
|
125
184
|
>;
|
|
126
|
-
const
|
|
185
|
+
const inputRequired = (inputSchema?.required ?? []) as string[];
|
|
127
186
|
|
|
128
|
-
const
|
|
187
|
+
const inputParams = Object.entries(inputProps)
|
|
129
188
|
.map(([key, val]) => {
|
|
130
|
-
const optional = !
|
|
189
|
+
const optional = !inputRequired.includes(key) ? "?" : "";
|
|
131
190
|
const type =
|
|
132
|
-
val.type ===
|
|
133
|
-
?
|
|
134
|
-
: val.type ===
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const comment = val.description ? ` // ${val.description}` :
|
|
191
|
+
val.type === "number"
|
|
192
|
+
? "number"
|
|
193
|
+
: val.type === "boolean"
|
|
194
|
+
? "boolean"
|
|
195
|
+
: val.type === "array"
|
|
196
|
+
? "unknown[]"
|
|
197
|
+
: val.type === "object"
|
|
198
|
+
? "Record<string, unknown>"
|
|
199
|
+
: "string";
|
|
200
|
+
const comment = val.description ? ` // ${val.description}` : "";
|
|
142
201
|
return ` ${key}${optional}: ${type};${comment}`;
|
|
143
202
|
})
|
|
144
|
-
.join(
|
|
203
|
+
.join("\n");
|
|
145
204
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
)
|
|
205
|
+
let result = `interface ${safeName}Args {\n${inputParams}\n}`;
|
|
206
|
+
|
|
207
|
+
// Generate Response interface if outputs schema provided
|
|
208
|
+
if (outputSchema && Object.keys(outputSchema).length > 0) {
|
|
209
|
+
const responseType = this.schemaTypeToTs(outputSchema);
|
|
210
|
+
result += `\n\ntype ${safeName}Response = ${responseType};`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result;
|
|
150
214
|
}
|
|
151
215
|
|
|
152
216
|
/**
|
|
@@ -154,7 +218,7 @@ export class ServiceRegistry {
|
|
|
154
218
|
* OpenAI-compatible APIs require tool names to match ^[a-zA-Z0-9_-]+$
|
|
155
219
|
*/
|
|
156
220
|
private toLLMToolName(internalName: string): string {
|
|
157
|
-
return internalName.replace(/\./g,
|
|
221
|
+
return internalName.replace(/\./g, "_");
|
|
158
222
|
}
|
|
159
223
|
|
|
160
224
|
/**
|
|
@@ -169,11 +233,11 @@ export class ServiceRegistry {
|
|
|
169
233
|
}
|
|
170
234
|
}
|
|
171
235
|
// Fallback: convert first underscore to dot
|
|
172
|
-
const underscoreIndex = llmName.indexOf(
|
|
236
|
+
const underscoreIndex = llmName.indexOf("_");
|
|
173
237
|
if (underscoreIndex > 0) {
|
|
174
238
|
return (
|
|
175
239
|
llmName.substring(0, underscoreIndex) +
|
|
176
|
-
|
|
240
|
+
"." +
|
|
177
241
|
llmName.substring(underscoreIndex + 1)
|
|
178
242
|
);
|
|
179
243
|
}
|
|
@@ -231,7 +295,7 @@ export class ServiceRegistry {
|
|
|
231
295
|
.map((info) => {
|
|
232
296
|
const searchText = `${info.name} ${info.namespace} ${
|
|
233
297
|
info.procedure
|
|
234
|
-
} ${info.description ??
|
|
298
|
+
} ${info.description ?? ""}`.toLowerCase();
|
|
235
299
|
const matchCount = keywords.filter((kw) =>
|
|
236
300
|
searchText.includes(kw),
|
|
237
301
|
).length;
|
|
@@ -304,7 +368,7 @@ export class ServiceRegistry {
|
|
|
304
368
|
this.tools.keys(),
|
|
305
369
|
)
|
|
306
370
|
.slice(0, 10)
|
|
307
|
-
.join(
|
|
371
|
+
.join(", ")}`,
|
|
308
372
|
);
|
|
309
373
|
}
|
|
310
374
|
|
|
@@ -342,7 +406,7 @@ export class ServiceRegistry {
|
|
|
342
406
|
*/
|
|
343
407
|
export function generateServicesPrompt(registry: ServiceRegistry): string {
|
|
344
408
|
const namespaces = registry.getNamespaces();
|
|
345
|
-
if (namespaces.length === 0) return
|
|
409
|
+
if (namespaces.length === 0) return "";
|
|
346
410
|
|
|
347
411
|
const services = registry.getServiceInfo();
|
|
348
412
|
const byNamespace = new Map<string, ServiceToolInfo[]>();
|
|
@@ -353,7 +417,7 @@ export function generateServicesPrompt(registry: ServiceRegistry): string {
|
|
|
353
417
|
byNamespace.set(service.namespace, existing);
|
|
354
418
|
}
|
|
355
419
|
|
|
356
|
-
let prompt = `##
|
|
420
|
+
let prompt = `## Services\n\nThe following services are available for generated widgets to call:\n\n`;
|
|
357
421
|
|
|
358
422
|
for (const [ns, tools] of byNamespace) {
|
|
359
423
|
prompt += `### \`${ns}\`\n`;
|
|
@@ -362,16 +426,16 @@ export function generateServicesPrompt(registry: ServiceRegistry): string {
|
|
|
362
426
|
if (tool.description) {
|
|
363
427
|
prompt += `: ${tool.description}`;
|
|
364
428
|
}
|
|
365
|
-
prompt +=
|
|
429
|
+
prompt += "\n";
|
|
366
430
|
}
|
|
367
|
-
prompt +=
|
|
431
|
+
prompt += "\n";
|
|
368
432
|
}
|
|
369
433
|
|
|
370
434
|
prompt += `**Usage in widgets:**
|
|
371
435
|
\`\`\`tsx
|
|
372
436
|
// Services are available as global namespaces
|
|
373
|
-
const result = await ${namespaces[0] ??
|
|
374
|
-
byNamespace.get(namespaces[0] ??
|
|
437
|
+
const result = await ${namespaces[0] ?? "service"}.${
|
|
438
|
+
byNamespace.get(namespaces[0] ?? "")?.[0]?.procedure ?? "example"
|
|
375
439
|
}({ /* args */ });
|
|
376
440
|
\`\`\`
|
|
377
441
|
|
package/src/server/vfs-routes.ts
CHANGED
|
@@ -40,7 +40,7 @@ async function listAllFiles(
|
|
|
40
40
|
relPath: string,
|
|
41
41
|
): Promise<string[]> {
|
|
42
42
|
const targetPath = resolvePath(rootDir, relPath);
|
|
43
|
-
let entries
|
|
43
|
+
let entries;
|
|
44
44
|
try {
|
|
45
45
|
entries = await readdir(targetPath, { withFileTypes: true });
|
|
46
46
|
} catch {
|
|
@@ -49,7 +49,7 @@ async function listAllFiles(
|
|
|
49
49
|
|
|
50
50
|
const results: string[] = [];
|
|
51
51
|
for (const entry of entries) {
|
|
52
|
-
const entryRelPath = joinRelPath(relPath, entry.name);
|
|
52
|
+
const entryRelPath = joinRelPath(relPath, String(entry.name));
|
|
53
53
|
if (entry.isDirectory()) {
|
|
54
54
|
results.push(...(await listAllFiles(rootDir, entryRelPath)));
|
|
55
55
|
} else {
|