@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/executor.js
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local execution engine for TypeScript SDK components.
|
|
3
|
+
* Mirrors the Python CLI executor's component-type dispatch semantics.
|
|
4
|
+
*/
|
|
5
|
+
import { parseJsonResponse } from "./json.js";
|
|
6
|
+
const COMPONENT_ERROR_KEY = "_component_error";
|
|
7
|
+
const OUTPUT_SCHEMA_MISMATCH_REASON = "Model response doesn't match the requested output schema.";
|
|
8
|
+
const JSON_OUTPUT_ERROR_MARKERS = [
|
|
9
|
+
"unterminated string in json",
|
|
10
|
+
"unexpected end of json input",
|
|
11
|
+
"jsondecodeerror",
|
|
12
|
+
"expecting value",
|
|
13
|
+
"extra data",
|
|
14
|
+
"unexpected token",
|
|
15
|
+
];
|
|
16
|
+
function buildComponentFailureOutput(error) {
|
|
17
|
+
return { [COMPONENT_ERROR_KEY]: error };
|
|
18
|
+
}
|
|
19
|
+
function isOutputSchemaMismatchError(error) {
|
|
20
|
+
const normalized = error.toLowerCase();
|
|
21
|
+
return normalized.includes("output doesn't match declared output_schema")
|
|
22
|
+
|| JSON_OUTPUT_ERROR_MARKERS.some((marker) => normalized.includes(marker));
|
|
23
|
+
}
|
|
24
|
+
function buildSchemaMismatchEvalOutput(error) {
|
|
25
|
+
return {
|
|
26
|
+
score: 0.0,
|
|
27
|
+
passed: false,
|
|
28
|
+
reason: OUTPUT_SCHEMA_MISMATCH_REASON,
|
|
29
|
+
output_schema_error: error,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Eval-input contract helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
function stableStringify(value) {
|
|
36
|
+
if (value === null || typeof value !== "object") {
|
|
37
|
+
return JSON.stringify(value);
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
41
|
+
}
|
|
42
|
+
const obj = value;
|
|
43
|
+
const keys = Object.keys(obj).sort();
|
|
44
|
+
const parts = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(obj[key])}`);
|
|
45
|
+
return `{${parts.join(",")}}`;
|
|
46
|
+
}
|
|
47
|
+
function stringifyValue(value) {
|
|
48
|
+
if (value == null) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return stableStringify(value);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function deriveModelInput(row) {
|
|
62
|
+
if ("input" in row) {
|
|
63
|
+
return stringifyValue(row["input"]);
|
|
64
|
+
}
|
|
65
|
+
return stringifyValue(row);
|
|
66
|
+
}
|
|
67
|
+
function deriveExpectedOutput(row) {
|
|
68
|
+
if ("expected_output" in row) {
|
|
69
|
+
return stringifyValue(row["expected_output"]);
|
|
70
|
+
}
|
|
71
|
+
if ("output" in row) {
|
|
72
|
+
return stringifyValue(row["output"]);
|
|
73
|
+
}
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
function deriveModelResponse(output) {
|
|
77
|
+
const keys = Object.keys(output);
|
|
78
|
+
if (keys.length === 1 && keys[0] === "result") {
|
|
79
|
+
return stringifyValue(output["result"]);
|
|
80
|
+
}
|
|
81
|
+
if (keys.length === 1 && keys[0] === "output") {
|
|
82
|
+
return stringifyValue(output["output"]);
|
|
83
|
+
}
|
|
84
|
+
return stringifyValue(output);
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Component execution dispatch
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
function isConditionalLike(value) {
|
|
90
|
+
return (value != null &&
|
|
91
|
+
typeof value === "object" &&
|
|
92
|
+
typeof value.condition === "function" &&
|
|
93
|
+
typeof value.ifTrue === "string" &&
|
|
94
|
+
typeof value.ifFalse === "string");
|
|
95
|
+
}
|
|
96
|
+
function isLeafLike(value) {
|
|
97
|
+
return (value != null &&
|
|
98
|
+
typeof value === "object" &&
|
|
99
|
+
"eval" in value &&
|
|
100
|
+
!("threshold" in value));
|
|
101
|
+
}
|
|
102
|
+
async function executeFn(fn, kwargs, isAsync) {
|
|
103
|
+
if (isAsync) {
|
|
104
|
+
return await fn(kwargs);
|
|
105
|
+
}
|
|
106
|
+
return fn(kwargs);
|
|
107
|
+
}
|
|
108
|
+
function stringifyLogValue(value) {
|
|
109
|
+
if (typeof value === "string")
|
|
110
|
+
return value;
|
|
111
|
+
try {
|
|
112
|
+
return JSON.stringify(value);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return String(value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function captureConsoleLogs(fn) {
|
|
119
|
+
const logs = [];
|
|
120
|
+
const originals = {
|
|
121
|
+
log: console.log,
|
|
122
|
+
info: console.info,
|
|
123
|
+
warn: console.warn,
|
|
124
|
+
error: console.error,
|
|
125
|
+
};
|
|
126
|
+
const makeCapture = (stream) => (...args) => {
|
|
127
|
+
logs.push({
|
|
128
|
+
stream,
|
|
129
|
+
message: args.map(stringifyLogValue).join(" "),
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
console.log = makeCapture("stdout");
|
|
133
|
+
console.info = makeCapture("stdout");
|
|
134
|
+
console.warn = makeCapture("stderr");
|
|
135
|
+
console.error = makeCapture("stderr");
|
|
136
|
+
try {
|
|
137
|
+
return { value: await fn(), logs };
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
console.log = originals.log;
|
|
141
|
+
console.info = originals.info;
|
|
142
|
+
console.warn = originals.warn;
|
|
143
|
+
console.error = originals.error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function runTool(comp, kwargs) {
|
|
147
|
+
const result = await executeFn(comp.fn, kwargs, comp.isAsync);
|
|
148
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
return { result };
|
|
152
|
+
}
|
|
153
|
+
async function runCodeEval(comp, kwargs) {
|
|
154
|
+
const { value: result, logs } = await captureConsoleLogs(async () => executeFn(comp.fn, kwargs, comp.isAsync));
|
|
155
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
156
|
+
return logs.length > 0
|
|
157
|
+
? { ...result, logs }
|
|
158
|
+
: result;
|
|
159
|
+
}
|
|
160
|
+
return logs.length > 0 ? { score: result, logs } : { score: result };
|
|
161
|
+
}
|
|
162
|
+
async function runSkill(comp, _kwargs) {
|
|
163
|
+
throw new Error(`Skill '${comp.name}' is a non-executable configuration component and cannot execute locally.`);
|
|
164
|
+
}
|
|
165
|
+
function isJsonSchemaObject(schema) {
|
|
166
|
+
return schema != null && typeof schema === "object" && schema.type === "object";
|
|
167
|
+
}
|
|
168
|
+
function isJsonSchemaArray(schema) {
|
|
169
|
+
return schema != null && typeof schema === "object" && schema.type === "array";
|
|
170
|
+
}
|
|
171
|
+
function validateSchemaValue(value, schema, path = "$") {
|
|
172
|
+
if (schema == null || typeof schema !== "object") {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
if (isJsonSchemaObject(schema)) {
|
|
176
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
177
|
+
return [`${path} must be an object`];
|
|
178
|
+
}
|
|
179
|
+
const obj = value;
|
|
180
|
+
const errors = [];
|
|
181
|
+
for (const key of schema.required ?? []) {
|
|
182
|
+
if (!(key in obj)) {
|
|
183
|
+
errors.push(`${path}.${key} is required`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const properties = schema.properties ?? {};
|
|
187
|
+
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
188
|
+
if (key in obj) {
|
|
189
|
+
errors.push(...validateSchemaValue(obj[key], propertySchema, `${path}.${key}`));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (schema.additionalProperties === false) {
|
|
193
|
+
for (const key of Object.keys(obj)) {
|
|
194
|
+
if (!(key in properties)) {
|
|
195
|
+
errors.push(`${path}.${key} is not allowed`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return errors;
|
|
200
|
+
}
|
|
201
|
+
if (isJsonSchemaArray(schema)) {
|
|
202
|
+
if (!Array.isArray(value)) {
|
|
203
|
+
return [`${path} must be an array`];
|
|
204
|
+
}
|
|
205
|
+
return value.flatMap((item, index) => validateSchemaValue(item, schema.items, `${path}[${index}]`));
|
|
206
|
+
}
|
|
207
|
+
const expectedType = schema.type;
|
|
208
|
+
if (typeof expectedType !== "string") {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
switch (expectedType) {
|
|
212
|
+
case "string":
|
|
213
|
+
return typeof value === "string" ? [] : [`${path} must be a string`];
|
|
214
|
+
case "number":
|
|
215
|
+
return typeof value === "number" ? [] : [`${path} must be a number`];
|
|
216
|
+
case "boolean":
|
|
217
|
+
return typeof value === "boolean" ? [] : [`${path} must be a boolean`];
|
|
218
|
+
case "null":
|
|
219
|
+
return value === null ? [] : [`${path} must be null`];
|
|
220
|
+
default:
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function validateOutputSchema(componentName, output, schema) {
|
|
225
|
+
const errors = validateSchemaValue(output, schema);
|
|
226
|
+
if (errors.length > 0) {
|
|
227
|
+
throw new Error(`Output for component '${componentName}' failed schema validation: ${errors.join("; ")}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function runRunnerComponent(comp, kwargs) {
|
|
231
|
+
if (comp.runner == null) {
|
|
232
|
+
throw new Error(`${comp.constructor.name} '${comp.name}' requires a runner to execute locally. Provide one when constructing the ${comp.constructor.name}.`);
|
|
233
|
+
}
|
|
234
|
+
if (!comp.model || comp.model.trim() === "") {
|
|
235
|
+
throw new Error(`Model is required for component '${comp.name}'. Set component.model explicitly.`);
|
|
236
|
+
}
|
|
237
|
+
const messages = comp.runner.buildMessages(comp, kwargs);
|
|
238
|
+
const response = await comp.runner.callLlm({
|
|
239
|
+
messages,
|
|
240
|
+
model: comp.model,
|
|
241
|
+
tools: "tools" in comp ? comp.tools : [],
|
|
242
|
+
outputSchema: comp.outputSchema,
|
|
243
|
+
jsonRepair: comp.jsonRepair,
|
|
244
|
+
});
|
|
245
|
+
const hasTextOutput = response.text.trim() !== "";
|
|
246
|
+
const hasToolCalls = response.toolCalls.length > 0;
|
|
247
|
+
let output;
|
|
248
|
+
if (!hasTextOutput) {
|
|
249
|
+
output = {};
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = parseJsonResponse(response.text);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
parsed = { result: response.text };
|
|
258
|
+
}
|
|
259
|
+
output = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
260
|
+
? parsed
|
|
261
|
+
: { result: parsed };
|
|
262
|
+
}
|
|
263
|
+
if (!hasToolCalls && hasTextOutput) {
|
|
264
|
+
validateOutputSchema(comp.name, output, comp.outputSchema);
|
|
265
|
+
}
|
|
266
|
+
if (hasToolCalls) {
|
|
267
|
+
output._toolCalls = response.toolCalls.map((toolCall) => ({
|
|
268
|
+
name: toolCall.name,
|
|
269
|
+
input: toolCall.input,
|
|
270
|
+
id: toolCall.id,
|
|
271
|
+
...(toolCall._parseError ? { _parseError: toolCall._parseError } : {}),
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
if (response.stopReason != null) {
|
|
275
|
+
output._stopReason = response.stopReason;
|
|
276
|
+
}
|
|
277
|
+
return output;
|
|
278
|
+
}
|
|
279
|
+
async function runAgent(comp, kwargs) {
|
|
280
|
+
return runRunnerComponent(comp, kwargs);
|
|
281
|
+
}
|
|
282
|
+
async function runLLMProcessor(comp, kwargs) {
|
|
283
|
+
return runRunnerComponent(comp, kwargs);
|
|
284
|
+
}
|
|
285
|
+
async function runLLMEval(comp, kwargs) {
|
|
286
|
+
return runRunnerComponent(comp, kwargs);
|
|
287
|
+
}
|
|
288
|
+
async function runOrchestration(comp, inputData, executedSteps) {
|
|
289
|
+
// Flatten input into accumulated so steps can access row fields directly.
|
|
290
|
+
// Each step's output is also merged at the top level AND stored at step.name.
|
|
291
|
+
const accumulated = { ...inputData };
|
|
292
|
+
const steps = new Map(comp.steps.map((s) => [s.name, s]));
|
|
293
|
+
// Find the first step (not referenced as a next target by any other step)
|
|
294
|
+
const allNextTargets = new Set();
|
|
295
|
+
for (const s of comp.steps) {
|
|
296
|
+
if (isConditionalLike(s.component)) {
|
|
297
|
+
allNextTargets.add(s.component.ifTrue);
|
|
298
|
+
allNextTargets.add(s.component.ifFalse);
|
|
299
|
+
}
|
|
300
|
+
for (const n of s.next) {
|
|
301
|
+
allNextTargets.add(n);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const startCandidates = comp.steps.filter((s) => !allNextTargets.has(s.name));
|
|
305
|
+
let currentStepName = startCandidates.length > 0
|
|
306
|
+
? startCandidates[0].name
|
|
307
|
+
: comp.steps[0]?.name ?? "";
|
|
308
|
+
let lastOutput = {};
|
|
309
|
+
const visited = new Set();
|
|
310
|
+
while (currentStepName && !visited.has(currentStepName)) {
|
|
311
|
+
visited.add(currentStepName);
|
|
312
|
+
const step = steps.get(currentStepName);
|
|
313
|
+
if (!step)
|
|
314
|
+
break;
|
|
315
|
+
const component = step.component;
|
|
316
|
+
if (isConditionalLike(component)) {
|
|
317
|
+
const result = component.condition(accumulated);
|
|
318
|
+
if (executedSteps) {
|
|
319
|
+
executedSteps.push({
|
|
320
|
+
name: step.name,
|
|
321
|
+
type: "conditional",
|
|
322
|
+
input: { ...accumulated },
|
|
323
|
+
output: { branch: result ? "if_true" : "if_false" },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
currentStepName = result ? component.ifTrue : component.ifFalse;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const stepInput = { ...accumulated };
|
|
330
|
+
const stepOutput = await executeComponent(component, accumulated);
|
|
331
|
+
if (executedSteps) {
|
|
332
|
+
executedSteps.push({
|
|
333
|
+
name: step.name,
|
|
334
|
+
type: component.componentType,
|
|
335
|
+
input: stepInput,
|
|
336
|
+
output: stepOutput,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// Merge step output at top level for convenient access in downstream steps
|
|
340
|
+
Object.assign(accumulated, stepOutput);
|
|
341
|
+
// Also store under the step name for named access
|
|
342
|
+
accumulated[step.name] = stepOutput;
|
|
343
|
+
lastOutput = stepOutput;
|
|
344
|
+
currentStepName = step.next[0] ?? "";
|
|
345
|
+
}
|
|
346
|
+
return lastOutput;
|
|
347
|
+
}
|
|
348
|
+
async function runChecklist(comp, kwargs) {
|
|
349
|
+
const scores = [];
|
|
350
|
+
const weights = comp.weights ?? comp.evals.map(() => 1.0);
|
|
351
|
+
const details = [];
|
|
352
|
+
for (const evalComp of comp.evals) {
|
|
353
|
+
const result = await executeComponent(evalComp, kwargs);
|
|
354
|
+
const score = Number(result.score ?? 0);
|
|
355
|
+
scores.push(score);
|
|
356
|
+
details.push({ name: evalComp.name, score, ...result });
|
|
357
|
+
}
|
|
358
|
+
const weightedSum = scores.reduce((sum, s, i) => sum + s * weights[i], 0);
|
|
359
|
+
const totalWeight = weights.reduce((sum, w) => sum + w, 0);
|
|
360
|
+
const finalScore = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
361
|
+
return { score: finalScore, details };
|
|
362
|
+
}
|
|
363
|
+
async function evalBranch(branch, kwargs) {
|
|
364
|
+
const branchResult = await executeComponent(branch.eval, kwargs);
|
|
365
|
+
const branchScore = Number(branchResult.score ?? 0);
|
|
366
|
+
const tookPass = branchScore >= branch.threshold;
|
|
367
|
+
const edges = tookPass ? branch.ifPass : branch.ifFail;
|
|
368
|
+
const leafScores = [];
|
|
369
|
+
const details = [];
|
|
370
|
+
const children = [];
|
|
371
|
+
details.push({
|
|
372
|
+
type: "branch",
|
|
373
|
+
name: branch.eval.name,
|
|
374
|
+
score: branchScore,
|
|
375
|
+
threshold: branch.threshold,
|
|
376
|
+
selected_branch: tookPass ? "if_pass" : "if_fail",
|
|
377
|
+
result: branchResult,
|
|
378
|
+
});
|
|
379
|
+
for (const edge of edges) {
|
|
380
|
+
if (isLeafLike(edge.node)) {
|
|
381
|
+
const result = await executeComponent(edge.node.eval, kwargs);
|
|
382
|
+
const score = Number(result.score ?? 0);
|
|
383
|
+
leafScores.push([score, edge.weight]);
|
|
384
|
+
const leafNode = {
|
|
385
|
+
type: "leaf",
|
|
386
|
+
name: edge.node.eval.name,
|
|
387
|
+
score,
|
|
388
|
+
weight: edge.weight,
|
|
389
|
+
result,
|
|
390
|
+
};
|
|
391
|
+
details.push(leafNode);
|
|
392
|
+
children.push(leafNode);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const [subScore, subDetails, subTree] = await evalBranch(edge.node, kwargs);
|
|
396
|
+
leafScores.push([subScore, edge.weight]);
|
|
397
|
+
details.push(...subDetails);
|
|
398
|
+
children.push({
|
|
399
|
+
...subTree,
|
|
400
|
+
weight: edge.weight,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const weightedSum = leafScores.reduce((sum, [s, w]) => sum + s * w, 0);
|
|
405
|
+
const totalWeight = leafScores.reduce((sum, [, w]) => sum + w, 0);
|
|
406
|
+
const finalScore = totalWeight > 0 ? weightedSum / totalWeight : 0;
|
|
407
|
+
return [
|
|
408
|
+
finalScore,
|
|
409
|
+
details,
|
|
410
|
+
{
|
|
411
|
+
type: "branch",
|
|
412
|
+
name: branch.eval.name,
|
|
413
|
+
score: finalScore,
|
|
414
|
+
threshold: branch.threshold,
|
|
415
|
+
routing_score: branchScore,
|
|
416
|
+
selected_branch: tookPass ? "if_pass" : "if_fail",
|
|
417
|
+
routing_result: branchResult,
|
|
418
|
+
children,
|
|
419
|
+
},
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
async function runEvalTree(comp, kwargs) {
|
|
423
|
+
const [score, details, result] = await evalBranch(comp.root, kwargs);
|
|
424
|
+
return { score, details, result };
|
|
425
|
+
}
|
|
426
|
+
export async function executeComponent(comp, kwargs) {
|
|
427
|
+
switch (comp.componentType) {
|
|
428
|
+
case "tool":
|
|
429
|
+
return runTool(comp, kwargs);
|
|
430
|
+
case "code_eval":
|
|
431
|
+
return runCodeEval(comp, kwargs);
|
|
432
|
+
case "skill":
|
|
433
|
+
return runSkill(comp, kwargs);
|
|
434
|
+
case "agent":
|
|
435
|
+
return runAgent(comp, kwargs);
|
|
436
|
+
case "llm_processor":
|
|
437
|
+
return runLLMProcessor(comp, kwargs);
|
|
438
|
+
case "llm_eval":
|
|
439
|
+
return runLLMEval(comp, kwargs);
|
|
440
|
+
case "orchestration":
|
|
441
|
+
return runOrchestration(comp, kwargs);
|
|
442
|
+
case "checklist":
|
|
443
|
+
return runChecklist(comp, kwargs);
|
|
444
|
+
case "eval_tree":
|
|
445
|
+
return runEvalTree(comp, kwargs);
|
|
446
|
+
default:
|
|
447
|
+
throw new Error(`Unsupported component type: ${comp.componentType}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
// Row execution (component + optional eval)
|
|
452
|
+
// ---------------------------------------------------------------------------
|
|
453
|
+
export async function executeRow(component, evalComponent, row) {
|
|
454
|
+
let output;
|
|
455
|
+
let error;
|
|
456
|
+
let demotedError;
|
|
457
|
+
let orchestrationSteps;
|
|
458
|
+
try {
|
|
459
|
+
if (component.componentType === "orchestration") {
|
|
460
|
+
orchestrationSteps = [];
|
|
461
|
+
output = await runOrchestration(component, row, orchestrationSteps);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
output = await executeComponent(component, row);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
error = err instanceof Error ? err.message : String(err);
|
|
469
|
+
if (evalComponent != null) {
|
|
470
|
+
demotedError = error;
|
|
471
|
+
output = buildComponentFailureOutput(error);
|
|
472
|
+
error = undefined;
|
|
473
|
+
}
|
|
474
|
+
else {
|
|
475
|
+
return { status: "error", error, pathTaken: buildPathTaken(component, row, undefined, error, orchestrationSteps) };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (evalComponent != null) {
|
|
479
|
+
if (output != null
|
|
480
|
+
&& typeof output[COMPONENT_ERROR_KEY] === "string"
|
|
481
|
+
&& isOutputSchemaMismatchError(String(output[COMPONENT_ERROR_KEY]))) {
|
|
482
|
+
return {
|
|
483
|
+
status: "ok",
|
|
484
|
+
output: { ...output, _eval: buildSchemaMismatchEvalOutput(String(output[COMPONENT_ERROR_KEY])) },
|
|
485
|
+
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const evalKwargs = {
|
|
489
|
+
model_input: deriveModelInput(row),
|
|
490
|
+
model_response: deriveModelResponse(output),
|
|
491
|
+
expected_output: deriveExpectedOutput(row),
|
|
492
|
+
};
|
|
493
|
+
try {
|
|
494
|
+
const evalOutput = await executeComponent(evalComponent, evalKwargs);
|
|
495
|
+
return {
|
|
496
|
+
status: "ok",
|
|
497
|
+
output: { ...output, _eval: evalOutput },
|
|
498
|
+
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
catch (err) {
|
|
502
|
+
const evalError = err instanceof Error ? err.message : String(err);
|
|
503
|
+
return {
|
|
504
|
+
status: "ok",
|
|
505
|
+
output,
|
|
506
|
+
error: evalError,
|
|
507
|
+
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
status: "ok",
|
|
513
|
+
output,
|
|
514
|
+
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function buildPathTaken(component, inputData, output, error, orchestrationSteps) {
|
|
518
|
+
switch (component.componentType) {
|
|
519
|
+
case "tool": {
|
|
520
|
+
const result = {
|
|
521
|
+
kind: "tool",
|
|
522
|
+
function_definition: {
|
|
523
|
+
name: component.name,
|
|
524
|
+
description: component.description,
|
|
525
|
+
},
|
|
526
|
+
input: inputData,
|
|
527
|
+
};
|
|
528
|
+
if (output !== undefined)
|
|
529
|
+
result.output = output;
|
|
530
|
+
if (error !== undefined)
|
|
531
|
+
result.error = error;
|
|
532
|
+
return result;
|
|
533
|
+
}
|
|
534
|
+
case "skill": {
|
|
535
|
+
const result = {
|
|
536
|
+
kind: "skill",
|
|
537
|
+
prompt: component.instructions ?? "",
|
|
538
|
+
};
|
|
539
|
+
if (error !== undefined)
|
|
540
|
+
result.error = error;
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
543
|
+
case "agent": {
|
|
544
|
+
const result = {
|
|
545
|
+
kind: "agent",
|
|
546
|
+
messages: [],
|
|
547
|
+
};
|
|
548
|
+
if (error !== undefined)
|
|
549
|
+
result.error = error;
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
case "orchestration": {
|
|
553
|
+
const result = {
|
|
554
|
+
kind: "orchestration",
|
|
555
|
+
steps: orchestrationSteps ?? [],
|
|
556
|
+
};
|
|
557
|
+
if (error !== undefined)
|
|
558
|
+
result.error = error;
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
default:
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zhanla-sdk-ts public API.
|
|
3
|
+
* Import component classes to define zhanla components in TypeScript.
|
|
4
|
+
*/
|
|
5
|
+
export { Tool, CodeEval, Skill, Agent, LLMProcessor, LLMEval, Runner, Conditional, Step, Orchestration, Leaf, Edge, Branch, Checklist, EvalTree, BaseComponent, RUNNABLE_TYPES, EVAL_TYPES, } from "./types.js";
|
|
6
|
+
export type { ToolOptions, CodeEvalOptions, SkillOptions, AgentOptions, LLMProcessorOptions, LLMEvalOptions, OrchestrationOptions, ChecklistOptions, EvalTreeOptions, StepOptions, ComponentType, JsonSchema, JsonSchemaObject, JsonSchemaArray, JsonSchemaScalar, ToolCall, LLMResponse, RunnerOptions, RunnerCallOptions, RunnerMessage, } from "./types.js";
|
|
7
|
+
export { toManifest, collectExportedComponents } from "./manifest.js";
|
|
8
|
+
export type { ComponentManifest, StepManifest, BranchManifest, EdgeManifest, LeafManifest } from "./manifest.js";
|
|
9
|
+
export { executeComponent, executeRow } from "./executor.js";
|
|
10
|
+
export type { RowResult } from "./executor.js";
|
|
11
|
+
export { wrap } from "./wrap.js";
|
|
12
|
+
export { parseJsonResponse } from "./json.js";
|
|
13
|
+
export { TraceContext, traceStorage } from "./trace_store.js";
|
|
14
|
+
export type { LLMCall } from "./trace_store.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zhanla-sdk-ts public API.
|
|
3
|
+
* Import component classes to define zhanla components in TypeScript.
|
|
4
|
+
*/
|
|
5
|
+
export { Tool, CodeEval, Skill, Agent, LLMProcessor, LLMEval, Runner, Conditional, Step, Orchestration, Leaf, Edge, Branch, Checklist, EvalTree, BaseComponent, RUNNABLE_TYPES, EVAL_TYPES, } from "./types.js";
|
|
6
|
+
export { toManifest, collectExportedComponents } from "./manifest.js";
|
|
7
|
+
export { executeComponent, executeRow } from "./executor.js";
|
|
8
|
+
export { wrap } from "./wrap.js";
|
|
9
|
+
export { parseJsonResponse } from "./json.js";
|
|
10
|
+
export { TraceContext, traceStorage } from "./trace_store.js";
|
package/dist/json.d.ts
ADDED
package/dist/json.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for extracting JSON payloads from model text responses.
|
|
3
|
+
*/
|
|
4
|
+
function previewText(text, limit = 200) {
|
|
5
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
6
|
+
if (normalized.length <= limit) {
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
return `${normalized.slice(0, limit)}...`;
|
|
10
|
+
}
|
|
11
|
+
function extractMalformedComponentError(normalized) {
|
|
12
|
+
const startMatch = normalized.match(/^\{\s*"_component_error"\s*:\s*"/);
|
|
13
|
+
const endMatch = /"\s*\}$/.exec(normalized);
|
|
14
|
+
if (!startMatch || !endMatch || endMatch.index < startMatch[0].length) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
_component_error: normalized.slice(startMatch[0].length, endMatch.index),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function parseJsonResponse(text) {
|
|
22
|
+
let normalized = text.trim();
|
|
23
|
+
if (normalized.startsWith("```")) {
|
|
24
|
+
const lines = normalized.split(/\r?\n/);
|
|
25
|
+
if (lines.length >= 3 && lines[0].startsWith("```") && lines[lines.length - 1].trim() === "```") {
|
|
26
|
+
normalized = lines.slice(1, -1).join("\n").trim();
|
|
27
|
+
if (normalized.toLowerCase().startsWith("json")) {
|
|
28
|
+
normalized = normalized.slice(4).trimStart();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(normalized);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const recovered = extractMalformedComponentError(normalized);
|
|
37
|
+
if (recovered != null) {
|
|
38
|
+
return recovered;
|
|
39
|
+
}
|
|
40
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
41
|
+
throw new SyntaxError(`Failed to parse JSON response: ${detail}. Response preview: ${previewText(normalized)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manifest emission — serialize SDK component instances into ComponentManifest objects.
|
|
3
|
+
* The manifest shape mirrors the Python CLI's ComponentManifest dataclass.
|
|
4
|
+
*/
|
|
5
|
+
import { BaseComponent, JsonSchema } from "./types.js";
|
|
6
|
+
export interface StepManifest {
|
|
7
|
+
name: string;
|
|
8
|
+
component: string;
|
|
9
|
+
next: string[];
|
|
10
|
+
is_conditional?: boolean;
|
|
11
|
+
if_true?: string;
|
|
12
|
+
if_false?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EdgeManifest {
|
|
15
|
+
weight: number;
|
|
16
|
+
node: LeafManifest | BranchManifest;
|
|
17
|
+
}
|
|
18
|
+
export interface LeafManifest {
|
|
19
|
+
type: "leaf";
|
|
20
|
+
eval: string;
|
|
21
|
+
}
|
|
22
|
+
export interface BranchManifest {
|
|
23
|
+
type: "branch";
|
|
24
|
+
eval: string;
|
|
25
|
+
threshold: number;
|
|
26
|
+
if_pass: EdgeManifest[];
|
|
27
|
+
if_fail: EdgeManifest[];
|
|
28
|
+
}
|
|
29
|
+
export interface ComponentManifest {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
component_type: string;
|
|
33
|
+
language: "typescript";
|
|
34
|
+
is_runnable: boolean;
|
|
35
|
+
is_eval: boolean;
|
|
36
|
+
version_hash: string;
|
|
37
|
+
instructions?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
fn_present?: boolean;
|
|
40
|
+
is_async?: boolean;
|
|
41
|
+
tools?: string[];
|
|
42
|
+
skills?: string[];
|
|
43
|
+
agents?: string[];
|
|
44
|
+
steps?: StepManifest[];
|
|
45
|
+
evals?: string[];
|
|
46
|
+
weights?: number[];
|
|
47
|
+
root?: BranchManifest;
|
|
48
|
+
output_schema?: JsonSchema;
|
|
49
|
+
file_path?: string;
|
|
50
|
+
symbol_name?: string;
|
|
51
|
+
source_code?: string;
|
|
52
|
+
source_language?: string;
|
|
53
|
+
source_format?: string;
|
|
54
|
+
model_response_format?: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function toManifest(component: BaseComponent, opts?: {
|
|
57
|
+
filePath?: string;
|
|
58
|
+
symbolName?: string;
|
|
59
|
+
}): ComponentManifest;
|
|
60
|
+
export declare function collectExportedComponents(moduleExports: Record<string, unknown>): BaseComponent[];
|