@zhanla/sdk-ts 0.1.0
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/README.md +347 -0
- package/bin/cli.js +62 -0
- package/bin/discover.js +70 -0
- package/bin/postinstall.js +37 -0
- package/bin/run.js +144 -0
- package/dist/executor.d.ts +13 -0
- package/dist/executor.js +564 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +10 -0
- package/dist/json.d.ts +4 -0
- package/dist/json.js +43 -0
- package/dist/manifest.d.ts +60 -0
- package/dist/manifest.js +275 -0
- package/dist/trace_store.d.ts +38 -0
- package/dist/trace_store.js +30 -0
- package/dist/types.d.ts +283 -0
- package/dist/types.js +697 -0
- package/dist/wrap.d.ts +37 -0
- package/dist/wrap.js +255 -0
- package/package.json +33 -0
package/dist/types.js
ADDED
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the bench TypeScript SDK.
|
|
3
|
+
* Mirrors the Python SDK's class-based component model.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { parseJsonResponse } from "./json.js";
|
|
7
|
+
import { isAnthropicClient, isGeminiClient, isOpenAIClient, wrap } from "./wrap.js";
|
|
8
|
+
export const RUNNABLE_TYPES = new Set([
|
|
9
|
+
"tool",
|
|
10
|
+
"agent",
|
|
11
|
+
"llm_processor",
|
|
12
|
+
"orchestration",
|
|
13
|
+
]);
|
|
14
|
+
export const EVAL_TYPES = new Set([
|
|
15
|
+
"code_eval",
|
|
16
|
+
"llm_eval",
|
|
17
|
+
"checklist",
|
|
18
|
+
"eval_tree",
|
|
19
|
+
]);
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Hashing utilities
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
function hashFields(...parts) {
|
|
24
|
+
const h = createHash("sha256");
|
|
25
|
+
for (const p of parts) {
|
|
26
|
+
h.update(p, "utf8");
|
|
27
|
+
}
|
|
28
|
+
return h.digest("hex");
|
|
29
|
+
}
|
|
30
|
+
function sortedStringify(value) {
|
|
31
|
+
if (value === null || typeof value !== "object") {
|
|
32
|
+
return JSON.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return "[" + value.map(sortedStringify).join(",") + "]";
|
|
36
|
+
}
|
|
37
|
+
const keys = Object.keys(value).sort();
|
|
38
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${sortedStringify(value[k])}`);
|
|
39
|
+
return "{" + parts.join(",") + "}";
|
|
40
|
+
}
|
|
41
|
+
function normalizeSchema(schema) {
|
|
42
|
+
if (schema == null)
|
|
43
|
+
return "";
|
|
44
|
+
return sortedStringify(schema);
|
|
45
|
+
}
|
|
46
|
+
function isPlainRecord(value) {
|
|
47
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
function cloneSchemaObject(schema) {
|
|
50
|
+
return { ...schema };
|
|
51
|
+
}
|
|
52
|
+
function validateToolInputSchema(schema) {
|
|
53
|
+
if (!isPlainRecord(schema)) {
|
|
54
|
+
throw new Error("Tool inputSchema must be a JSON Schema object.");
|
|
55
|
+
}
|
|
56
|
+
if (schema.type !== "object") {
|
|
57
|
+
throw new Error("Tool inputSchema must be a JSON Schema object with type 'object'.");
|
|
58
|
+
}
|
|
59
|
+
if (!isPlainRecord(schema.properties)) {
|
|
60
|
+
throw new Error("Tool inputSchema must define a properties object.");
|
|
61
|
+
}
|
|
62
|
+
return schema;
|
|
63
|
+
}
|
|
64
|
+
function schemaSummary(schema) {
|
|
65
|
+
const schemaRecord = isPlainRecord(schema)
|
|
66
|
+
? schema
|
|
67
|
+
: null;
|
|
68
|
+
const rawProperties = schemaRecord != null && isPlainRecord(schemaRecord["properties"])
|
|
69
|
+
? schemaRecord["properties"]
|
|
70
|
+
: null;
|
|
71
|
+
const properties = rawProperties != null
|
|
72
|
+
? Object.entries(rawProperties)
|
|
73
|
+
.map(([name, value]) => {
|
|
74
|
+
const type = isPlainRecord(value) && typeof value.type === "string"
|
|
75
|
+
? value.type
|
|
76
|
+
: "unknown";
|
|
77
|
+
return `${name}: ${type}`;
|
|
78
|
+
})
|
|
79
|
+
.join(", ")
|
|
80
|
+
: "";
|
|
81
|
+
return properties ? `Expected JSON object fields: ${properties}` : "Expected a JSON object.";
|
|
82
|
+
}
|
|
83
|
+
function previewText(text, limit = 160) {
|
|
84
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
85
|
+
return normalized.length <= limit ? normalized : `${normalized.slice(0, limit)}...`;
|
|
86
|
+
}
|
|
87
|
+
function fnSource(fn) {
|
|
88
|
+
if (fn == null)
|
|
89
|
+
return "";
|
|
90
|
+
return fn.toString();
|
|
91
|
+
}
|
|
92
|
+
function contentPartToText(part) {
|
|
93
|
+
if (typeof part === "string") {
|
|
94
|
+
return part;
|
|
95
|
+
}
|
|
96
|
+
if (part != null && typeof part === "object" && typeof part.text === "string") {
|
|
97
|
+
return part.text;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
function normalizeOpenAIContent(content) {
|
|
102
|
+
if (typeof content === "string") {
|
|
103
|
+
return content;
|
|
104
|
+
}
|
|
105
|
+
if (!Array.isArray(content)) {
|
|
106
|
+
return "";
|
|
107
|
+
}
|
|
108
|
+
return content.map((part) => {
|
|
109
|
+
if (part != null && typeof part === "object" && typeof part.text === "string") {
|
|
110
|
+
return part.text;
|
|
111
|
+
}
|
|
112
|
+
return "";
|
|
113
|
+
}).join("");
|
|
114
|
+
}
|
|
115
|
+
function normalizeGeminiText(response) {
|
|
116
|
+
if (typeof response["text"] === "string") {
|
|
117
|
+
return response["text"];
|
|
118
|
+
}
|
|
119
|
+
const candidates = Array.isArray(response["candidates"]) ? response["candidates"] : [];
|
|
120
|
+
const first = candidates[0];
|
|
121
|
+
if (first == null || typeof first !== "object") {
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
const content = first.content;
|
|
125
|
+
if (content == null || typeof content !== "object") {
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
128
|
+
const parts = Array.isArray(content.parts)
|
|
129
|
+
? content.parts
|
|
130
|
+
: [];
|
|
131
|
+
return parts.map(contentPartToText).join("");
|
|
132
|
+
}
|
|
133
|
+
function withClosedAdditionalProperties(schema) {
|
|
134
|
+
const copy = cloneSchemaObject(schema);
|
|
135
|
+
copy["additionalProperties"] = false;
|
|
136
|
+
return copy;
|
|
137
|
+
}
|
|
138
|
+
function toGeminiSchema(schema) {
|
|
139
|
+
const source = cloneSchemaObject(schema);
|
|
140
|
+
const result = {};
|
|
141
|
+
for (const [key, value] of Object.entries(source)) {
|
|
142
|
+
if (key === "required" || key === "additionalProperties" || key === "$schema") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (key === "type" && typeof value === "string") {
|
|
146
|
+
result[key] = value.toUpperCase();
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (key === "properties" && value != null && typeof value === "object" && !Array.isArray(value)) {
|
|
150
|
+
const properties = {};
|
|
151
|
+
for (const [propertyName, propertySchema] of Object.entries(value)) {
|
|
152
|
+
properties[propertyName] = toGeminiSchema(propertySchema);
|
|
153
|
+
}
|
|
154
|
+
result[key] = properties;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (key === "items" && value != null && typeof value === "object" && !Array.isArray(value)) {
|
|
158
|
+
result[key] = toGeminiSchema(value);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
result[key] = value;
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
function parseToolArguments(argumentsValue) {
|
|
166
|
+
if (argumentsValue != null && typeof argumentsValue === "object" && !Array.isArray(argumentsValue)) {
|
|
167
|
+
return { input: argumentsValue };
|
|
168
|
+
}
|
|
169
|
+
if (typeof argumentsValue === "string") {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = parseJsonResponse(argumentsValue);
|
|
172
|
+
if (parsed != null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
173
|
+
return { input: parsed };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { input: {}, parseError: argumentsValue };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { input: {} };
|
|
181
|
+
}
|
|
182
|
+
function getGeminiFunctionCall(part) {
|
|
183
|
+
if (part == null || typeof part !== "object") {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const record = part;
|
|
187
|
+
const functionCall = record["functionCall"] ?? record["function_call"];
|
|
188
|
+
return functionCall != null && typeof functionCall === "object"
|
|
189
|
+
? functionCall
|
|
190
|
+
: null;
|
|
191
|
+
}
|
|
192
|
+
export class Runner {
|
|
193
|
+
client;
|
|
194
|
+
constructor(opts) {
|
|
195
|
+
this.client = wrap(opts.client);
|
|
196
|
+
}
|
|
197
|
+
buildMessages(component, row) {
|
|
198
|
+
const messages = [];
|
|
199
|
+
if (component.instructions != null && component.instructions.trim() !== "") {
|
|
200
|
+
messages.push({
|
|
201
|
+
role: "system",
|
|
202
|
+
content: component.instructions,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
else if (component.description.trim() !== "") {
|
|
206
|
+
messages.push({
|
|
207
|
+
role: "system",
|
|
208
|
+
content: component.description,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
messages.push({
|
|
212
|
+
role: "user",
|
|
213
|
+
content: JSON.stringify(row),
|
|
214
|
+
});
|
|
215
|
+
return messages;
|
|
216
|
+
}
|
|
217
|
+
providerName() {
|
|
218
|
+
if (isAnthropicClient(this.client))
|
|
219
|
+
return "anthropic";
|
|
220
|
+
if (isOpenAIClient(this.client))
|
|
221
|
+
return "openai";
|
|
222
|
+
if (isGeminiClient(this.client))
|
|
223
|
+
return "gemini";
|
|
224
|
+
return "unknown";
|
|
225
|
+
}
|
|
226
|
+
buildJsonRepairMessages(text, outputSchema) {
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
role: "system",
|
|
230
|
+
content: [
|
|
231
|
+
"Repair malformed JSON.",
|
|
232
|
+
"Return only valid JSON that conforms to the requested schema.",
|
|
233
|
+
schemaSummary(outputSchema),
|
|
234
|
+
].join("\n"),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
role: "user",
|
|
238
|
+
content: `Malformed JSON:\n${text}`,
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
async requestJsonRepair(model, outputSchema, text) {
|
|
243
|
+
const response = await this.callLlm({
|
|
244
|
+
messages: this.buildJsonRepairMessages(text, outputSchema),
|
|
245
|
+
model,
|
|
246
|
+
tools: [],
|
|
247
|
+
outputSchema: undefined,
|
|
248
|
+
jsonRepair: false,
|
|
249
|
+
});
|
|
250
|
+
return response.text;
|
|
251
|
+
}
|
|
252
|
+
async maybeRepairJson(args) {
|
|
253
|
+
const { model, outputSchema, jsonRepair, text } = args;
|
|
254
|
+
if (!jsonRepair || outputSchema == null || text.trim() === "") {
|
|
255
|
+
return text;
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
parseJsonResponse(text);
|
|
259
|
+
return text;
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
const repairedText = await this.requestJsonRepair(model, outputSchema, text);
|
|
263
|
+
try {
|
|
264
|
+
parseJsonResponse(repairedText);
|
|
265
|
+
return repairedText;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
const provider = this.providerName();
|
|
269
|
+
throw new Error(`JSON repair failed for provider=${provider} model=${model} original_response_preview="${previewText(text)}" repair_attempt_preview="${previewText(repairedText)}"`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
async callLlm(opts) {
|
|
274
|
+
if (isAnthropicClient(this.client)) {
|
|
275
|
+
const systemMessage = opts.messages.find((message) => message.role === "system")?.content ?? "";
|
|
276
|
+
const chatMessages = opts.messages
|
|
277
|
+
.filter((message) => message.role !== "system")
|
|
278
|
+
.map((message) => ({
|
|
279
|
+
role: message.role,
|
|
280
|
+
content: message.content,
|
|
281
|
+
}));
|
|
282
|
+
const response = await this.client.messages.create({
|
|
283
|
+
model: opts.model,
|
|
284
|
+
max_tokens: 1024,
|
|
285
|
+
system: systemMessage,
|
|
286
|
+
messages: chatMessages,
|
|
287
|
+
tools: (opts.tools ?? []).map((tool) => ({
|
|
288
|
+
name: tool.name,
|
|
289
|
+
description: tool.description,
|
|
290
|
+
input_schema: withClosedAdditionalProperties(tool.inputSchema),
|
|
291
|
+
})),
|
|
292
|
+
});
|
|
293
|
+
const content = Array.isArray(response.content)
|
|
294
|
+
? response.content
|
|
295
|
+
: [];
|
|
296
|
+
const text = content
|
|
297
|
+
.filter((part) => part != null && typeof part === "object" && part.type === "text")
|
|
298
|
+
.map(contentPartToText)
|
|
299
|
+
.join("");
|
|
300
|
+
const toolCalls = content
|
|
301
|
+
.filter((part) => part != null && typeof part === "object" && part.type === "tool_use")
|
|
302
|
+
.map((part, index) => {
|
|
303
|
+
const toolUse = part;
|
|
304
|
+
return {
|
|
305
|
+
name: String(toolUse.name ?? ""),
|
|
306
|
+
input: toolUse.input ?? {},
|
|
307
|
+
id: String(toolUse.id ?? `tool_${index}`),
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
text: await this.maybeRepairJson({
|
|
312
|
+
model: opts.model,
|
|
313
|
+
outputSchema: opts.outputSchema,
|
|
314
|
+
jsonRepair: opts.jsonRepair,
|
|
315
|
+
text,
|
|
316
|
+
}),
|
|
317
|
+
toolCalls,
|
|
318
|
+
stopReason: typeof response.stop_reason === "string"
|
|
319
|
+
? response.stop_reason
|
|
320
|
+
: null,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (isOpenAIClient(this.client)) {
|
|
324
|
+
const response = await this.client.chat.completions.create({
|
|
325
|
+
model: opts.model,
|
|
326
|
+
messages: opts.messages.map((message) => ({
|
|
327
|
+
role: message.role,
|
|
328
|
+
content: message.content,
|
|
329
|
+
})),
|
|
330
|
+
tools: (opts.tools ?? []).map((tool) => ({
|
|
331
|
+
type: "function",
|
|
332
|
+
function: {
|
|
333
|
+
name: tool.name,
|
|
334
|
+
description: tool.description,
|
|
335
|
+
parameters: withClosedAdditionalProperties(tool.inputSchema),
|
|
336
|
+
},
|
|
337
|
+
})),
|
|
338
|
+
});
|
|
339
|
+
const choice = Array.isArray(response.choices)
|
|
340
|
+
? response.choices[0]
|
|
341
|
+
: null;
|
|
342
|
+
const message = choice != null && typeof choice === "object"
|
|
343
|
+
? choice.message ?? {}
|
|
344
|
+
: {};
|
|
345
|
+
const rawToolCalls = Array.isArray(message["tool_calls"]) ? message["tool_calls"] : [];
|
|
346
|
+
return {
|
|
347
|
+
text: await this.maybeRepairJson({
|
|
348
|
+
model: opts.model,
|
|
349
|
+
outputSchema: opts.outputSchema,
|
|
350
|
+
jsonRepair: opts.jsonRepair,
|
|
351
|
+
text: normalizeOpenAIContent(message["content"]),
|
|
352
|
+
}),
|
|
353
|
+
toolCalls: rawToolCalls.map((toolCall, index) => {
|
|
354
|
+
const toolCallObj = toolCall;
|
|
355
|
+
const parsed = parseToolArguments(toolCallObj.function?.arguments);
|
|
356
|
+
return {
|
|
357
|
+
name: String(toolCallObj.function?.name ?? ""),
|
|
358
|
+
input: parsed.input,
|
|
359
|
+
id: String(toolCallObj.id ?? `tool_${index}`),
|
|
360
|
+
...(parsed.parseError ? { _parseError: parsed.parseError } : {}),
|
|
361
|
+
};
|
|
362
|
+
}),
|
|
363
|
+
stopReason: choice != null && typeof choice === "object" && typeof choice.finish_reason === "string"
|
|
364
|
+
? choice.finish_reason
|
|
365
|
+
: null,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (isGeminiClient(this.client)) {
|
|
369
|
+
const systemMessage = opts.messages.find((message) => message.role === "system")?.content ?? "";
|
|
370
|
+
const contents = opts.messages
|
|
371
|
+
.filter((message) => message.role !== "system")
|
|
372
|
+
.map((message) => ({
|
|
373
|
+
role: message.role === "assistant" ? "model" : "user",
|
|
374
|
+
parts: [{ text: message.content }],
|
|
375
|
+
}));
|
|
376
|
+
const response = await this.client.models.generateContent({
|
|
377
|
+
model: opts.model,
|
|
378
|
+
contents,
|
|
379
|
+
config: {
|
|
380
|
+
systemInstruction: systemMessage,
|
|
381
|
+
tools: (opts.tools ?? []).map((tool) => ({
|
|
382
|
+
functionDeclarations: [{
|
|
383
|
+
name: tool.name,
|
|
384
|
+
description: tool.description,
|
|
385
|
+
parameters: toGeminiSchema(tool.inputSchema),
|
|
386
|
+
}],
|
|
387
|
+
})),
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
const candidates = Array.isArray(response.candidates)
|
|
391
|
+
? response.candidates
|
|
392
|
+
: [];
|
|
393
|
+
const first = candidates[0];
|
|
394
|
+
const parts = first != null && typeof first === "object"
|
|
395
|
+
&& first.content != null
|
|
396
|
+
&& Array.isArray(first.content?.parts)
|
|
397
|
+
? (first.content.parts)
|
|
398
|
+
: [];
|
|
399
|
+
const toolCalls = parts
|
|
400
|
+
.map((part) => ({ fnCall: getGeminiFunctionCall(part) }))
|
|
401
|
+
.filter((part) => part.fnCall != null)
|
|
402
|
+
.map((part, index) => {
|
|
403
|
+
const fnCall = part.fnCall ?? {};
|
|
404
|
+
const parsed = parseToolArguments(fnCall["args"] ?? fnCall["arguments"]);
|
|
405
|
+
return {
|
|
406
|
+
name: String(fnCall["name"] ?? ""),
|
|
407
|
+
input: parsed.input,
|
|
408
|
+
id: String(fnCall["id"] ?? `tool_${index}`),
|
|
409
|
+
...(parsed.parseError ? { _parseError: parsed.parseError } : {}),
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
return {
|
|
413
|
+
text: await this.maybeRepairJson({
|
|
414
|
+
model: opts.model,
|
|
415
|
+
outputSchema: opts.outputSchema,
|
|
416
|
+
jsonRepair: opts.jsonRepair,
|
|
417
|
+
text: normalizeGeminiText(response),
|
|
418
|
+
}),
|
|
419
|
+
toolCalls,
|
|
420
|
+
stopReason: first != null && typeof first === "object" && first.finishReason != null
|
|
421
|
+
? String(first.finishReason)
|
|
422
|
+
: null,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
throw new TypeError(`Runner does not support ${Object.getPrototypeOf(this.client)?.constructor?.name ?? typeof this.client}.`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
// Base component class
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
export class BaseComponent {
|
|
432
|
+
get isRunnable() {
|
|
433
|
+
return RUNNABLE_TYPES.has(this.componentType);
|
|
434
|
+
}
|
|
435
|
+
get isEval() {
|
|
436
|
+
return EVAL_TYPES.has(this.componentType);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
export class Tool extends BaseComponent {
|
|
440
|
+
componentType = "tool";
|
|
441
|
+
name;
|
|
442
|
+
description;
|
|
443
|
+
fn;
|
|
444
|
+
inputSchema;
|
|
445
|
+
outputSchema;
|
|
446
|
+
isAsync;
|
|
447
|
+
constructor(opts) {
|
|
448
|
+
super();
|
|
449
|
+
this.name = opts.name;
|
|
450
|
+
this.description = opts.description;
|
|
451
|
+
this.fn = opts.fn;
|
|
452
|
+
this.inputSchema = validateToolInputSchema(opts.inputSchema);
|
|
453
|
+
this.outputSchema = opts.outputSchema;
|
|
454
|
+
this.isAsync = opts.fn.constructor.name === "AsyncFunction";
|
|
455
|
+
}
|
|
456
|
+
versionHash() {
|
|
457
|
+
return hashFields(this.componentType, fnSource(this.fn), normalizeSchema(this.inputSchema), normalizeSchema(this.outputSchema));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
export class CodeEval extends BaseComponent {
|
|
461
|
+
componentType = "code_eval";
|
|
462
|
+
name;
|
|
463
|
+
description;
|
|
464
|
+
fn;
|
|
465
|
+
isAsync;
|
|
466
|
+
modelResponseFormat;
|
|
467
|
+
constructor(opts) {
|
|
468
|
+
super();
|
|
469
|
+
this.name = opts.name;
|
|
470
|
+
this.description = opts.description;
|
|
471
|
+
this.fn = opts.fn;
|
|
472
|
+
this.isAsync = opts.fn.constructor.name === "AsyncFunction";
|
|
473
|
+
this.modelResponseFormat = opts.modelResponseFormat ?? "JSON";
|
|
474
|
+
}
|
|
475
|
+
versionHash() {
|
|
476
|
+
return hashFields(this.componentType, fnSource(this.fn), this.modelResponseFormat);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
export class Skill extends BaseComponent {
|
|
480
|
+
componentType = "skill";
|
|
481
|
+
name;
|
|
482
|
+
description;
|
|
483
|
+
instructions;
|
|
484
|
+
tools;
|
|
485
|
+
fn;
|
|
486
|
+
outputSchema;
|
|
487
|
+
isAsync;
|
|
488
|
+
constructor(opts) {
|
|
489
|
+
super();
|
|
490
|
+
this.name = opts.name;
|
|
491
|
+
this.description = opts.description;
|
|
492
|
+
this.instructions = opts.instructions;
|
|
493
|
+
this.tools = opts.tools ?? [];
|
|
494
|
+
this.fn = opts.fn;
|
|
495
|
+
this.outputSchema = opts.outputSchema;
|
|
496
|
+
this.isAsync = opts.fn?.constructor.name === "AsyncFunction";
|
|
497
|
+
}
|
|
498
|
+
versionHash() {
|
|
499
|
+
const toolRefs = [...this.tools.map((t) => t.name)].sort().join(",");
|
|
500
|
+
return hashFields(this.componentType, this.instructions, toolRefs, fnSource(this.fn));
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
export class Agent extends BaseComponent {
|
|
504
|
+
componentType = "agent";
|
|
505
|
+
name;
|
|
506
|
+
description;
|
|
507
|
+
instructions;
|
|
508
|
+
model;
|
|
509
|
+
runner;
|
|
510
|
+
tools;
|
|
511
|
+
skills;
|
|
512
|
+
agents;
|
|
513
|
+
outputSchema;
|
|
514
|
+
jsonRepair;
|
|
515
|
+
constructor(opts) {
|
|
516
|
+
super();
|
|
517
|
+
this.name = opts.name;
|
|
518
|
+
this.description = opts.description;
|
|
519
|
+
this.instructions = opts.instructions;
|
|
520
|
+
this.model = opts.model;
|
|
521
|
+
this.runner = opts.runner;
|
|
522
|
+
this.tools = opts.tools ?? [];
|
|
523
|
+
this.skills = opts.skills ?? [];
|
|
524
|
+
this.agents = opts.agents ?? [];
|
|
525
|
+
this.outputSchema = opts.outputSchema;
|
|
526
|
+
this.jsonRepair = opts.jsonRepair ?? false;
|
|
527
|
+
}
|
|
528
|
+
versionHash() {
|
|
529
|
+
const toolRefs = [...this.tools.map((t) => t.name)].sort().join(",");
|
|
530
|
+
const skillRefs = [...this.skills.map((s) => s.name)].sort().join(",");
|
|
531
|
+
const agentRefs = [...this.agents.map((a) => a.name)].sort().join(",");
|
|
532
|
+
return hashFields(this.componentType, this.instructions, this.model, toolRefs, skillRefs, agentRefs, normalizeSchema(this.outputSchema));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
export class LLMProcessor extends BaseComponent {
|
|
536
|
+
componentType = "llm_processor";
|
|
537
|
+
name;
|
|
538
|
+
description;
|
|
539
|
+
instructions;
|
|
540
|
+
model;
|
|
541
|
+
runner;
|
|
542
|
+
outputSchema;
|
|
543
|
+
jsonRepair;
|
|
544
|
+
constructor(opts) {
|
|
545
|
+
super();
|
|
546
|
+
this.name = opts.name;
|
|
547
|
+
this.description = opts.description;
|
|
548
|
+
this.instructions = opts.instructions;
|
|
549
|
+
this.model = opts.model;
|
|
550
|
+
this.runner = opts.runner;
|
|
551
|
+
this.outputSchema = opts.outputSchema;
|
|
552
|
+
this.jsonRepair = opts.jsonRepair ?? false;
|
|
553
|
+
}
|
|
554
|
+
versionHash() {
|
|
555
|
+
return hashFields(this.componentType, this.instructions, this.model, normalizeSchema(this.outputSchema));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
export class LLMEval extends BaseComponent {
|
|
559
|
+
componentType = "llm_eval";
|
|
560
|
+
name;
|
|
561
|
+
description;
|
|
562
|
+
instructions;
|
|
563
|
+
model;
|
|
564
|
+
runner;
|
|
565
|
+
outputSchema;
|
|
566
|
+
jsonRepair;
|
|
567
|
+
constructor(opts) {
|
|
568
|
+
super();
|
|
569
|
+
this.name = opts.name;
|
|
570
|
+
this.description = opts.description;
|
|
571
|
+
this.instructions = opts.instructions;
|
|
572
|
+
this.model = opts.model;
|
|
573
|
+
this.runner = opts.runner;
|
|
574
|
+
this.outputSchema = opts.outputSchema;
|
|
575
|
+
this.jsonRepair = opts.jsonRepair ?? false;
|
|
576
|
+
}
|
|
577
|
+
versionHash() {
|
|
578
|
+
return hashFields(this.componentType, this.instructions, this.model, normalizeSchema(this.outputSchema));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
export class Conditional {
|
|
582
|
+
condition;
|
|
583
|
+
ifTrue;
|
|
584
|
+
ifFalse;
|
|
585
|
+
constructor(opts) {
|
|
586
|
+
this.condition = opts.condition;
|
|
587
|
+
this.ifTrue = opts.ifTrue;
|
|
588
|
+
this.ifFalse = opts.ifFalse;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
export class Step {
|
|
592
|
+
name;
|
|
593
|
+
component;
|
|
594
|
+
next;
|
|
595
|
+
constructor(opts) {
|
|
596
|
+
this.name = opts.name;
|
|
597
|
+
this.component = opts.component;
|
|
598
|
+
this.next = opts.next ?? [];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
export class Orchestration extends BaseComponent {
|
|
602
|
+
componentType = "orchestration";
|
|
603
|
+
name;
|
|
604
|
+
description;
|
|
605
|
+
steps;
|
|
606
|
+
constructor(opts) {
|
|
607
|
+
super();
|
|
608
|
+
this.name = opts.name;
|
|
609
|
+
this.description = opts.description;
|
|
610
|
+
this.steps = opts.steps;
|
|
611
|
+
}
|
|
612
|
+
versionHash() {
|
|
613
|
+
const stepParts = this.steps.map((s) => {
|
|
614
|
+
const compRef = s.component instanceof Conditional ? "conditional" : s.component.name;
|
|
615
|
+
return `${s.name}:${compRef}:${s.next.join(",")}`;
|
|
616
|
+
});
|
|
617
|
+
return hashFields(this.componentType, stepParts.join("|"));
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// ---------------------------------------------------------------------------
|
|
621
|
+
// Eval composition: Leaf, Edge, Branch
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
export class Leaf {
|
|
624
|
+
eval;
|
|
625
|
+
constructor(opts) {
|
|
626
|
+
this.eval = opts.eval;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
export class Edge {
|
|
630
|
+
weight;
|
|
631
|
+
node;
|
|
632
|
+
constructor(opts) {
|
|
633
|
+
this.weight = opts.weight;
|
|
634
|
+
this.node = opts.node;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
export class Branch {
|
|
638
|
+
eval;
|
|
639
|
+
threshold;
|
|
640
|
+
ifPass;
|
|
641
|
+
ifFail;
|
|
642
|
+
constructor(opts) {
|
|
643
|
+
this.eval = opts.eval;
|
|
644
|
+
this.threshold = opts.threshold;
|
|
645
|
+
this.ifPass = opts.ifPass;
|
|
646
|
+
this.ifFail = opts.ifFail;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
export class Checklist extends BaseComponent {
|
|
650
|
+
componentType = "checklist";
|
|
651
|
+
name;
|
|
652
|
+
description;
|
|
653
|
+
evals;
|
|
654
|
+
weights;
|
|
655
|
+
constructor(opts) {
|
|
656
|
+
super();
|
|
657
|
+
this.name = opts.name;
|
|
658
|
+
this.description = opts.description;
|
|
659
|
+
this.evals = opts.evals;
|
|
660
|
+
this.weights = opts.weights;
|
|
661
|
+
}
|
|
662
|
+
versionHash() {
|
|
663
|
+
const evalRefs = this.evals.map((e) => e.name).join(",");
|
|
664
|
+
const w = this.weights ? this.weights.join(",") : "";
|
|
665
|
+
return hashFields(this.componentType, evalRefs, w);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// ---------------------------------------------------------------------------
|
|
669
|
+
// EvalTree
|
|
670
|
+
// ---------------------------------------------------------------------------
|
|
671
|
+
function treeHashParts(node) {
|
|
672
|
+
if (node instanceof Leaf) {
|
|
673
|
+
return `leaf:${node.eval.name}`;
|
|
674
|
+
}
|
|
675
|
+
if (node instanceof Edge) {
|
|
676
|
+
return `edge:${node.weight}:${treeHashParts(node.node)}`;
|
|
677
|
+
}
|
|
678
|
+
// Branch
|
|
679
|
+
const passParts = node.ifPass.map(treeHashParts).join("|");
|
|
680
|
+
const failParts = node.ifFail.map(treeHashParts).join("|");
|
|
681
|
+
return `branch:${node.eval.name}:${node.threshold}:[${passParts}]:[${failParts}]`;
|
|
682
|
+
}
|
|
683
|
+
export class EvalTree extends BaseComponent {
|
|
684
|
+
componentType = "eval_tree";
|
|
685
|
+
name;
|
|
686
|
+
description;
|
|
687
|
+
root;
|
|
688
|
+
constructor(opts) {
|
|
689
|
+
super();
|
|
690
|
+
this.name = opts.name;
|
|
691
|
+
this.description = opts.description;
|
|
692
|
+
this.root = opts.root;
|
|
693
|
+
}
|
|
694
|
+
versionHash() {
|
|
695
|
+
return hashFields(this.componentType, treeHashParts(this.root));
|
|
696
|
+
}
|
|
697
|
+
}
|