@zhanla/sdk-ts 0.3.3 → 0.3.5
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/bin/cli.js +15 -2
- package/bin/discover.js +11 -2
- package/bin/run.js +341 -2
- package/dist/executor.d.ts +2 -0
- package/dist/executor.js +50 -17
- package/dist/index.d.ts +1 -1
- package/dist/manifest.d.ts +1 -3
- package/dist/manifest.js +2 -93
- package/dist/trace_store.d.ts +1 -1
- package/dist/types.d.ts +11 -4
- package/dist/types.js +13 -1
- package/dist/wrap.d.ts +9 -0
- package/dist/wrap.js +14 -5
- package/package.json +3 -2
- package/bin/env.js +0 -72
package/bin/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ if (process.env.BENCH_SDK_TS_LOADER_ACTIVE !== "1") {
|
|
|
30
30
|
const [, , subcommand, ...args] = process.argv;
|
|
31
31
|
|
|
32
32
|
if (!subcommand) {
|
|
33
|
-
console.error("Usage: zhanla-sdk-ts <discover|run> <target>");
|
|
33
|
+
console.error("Usage: zhanla-sdk-ts <discover|run|eval-only> <target>");
|
|
34
34
|
process.exit(1);
|
|
35
35
|
}
|
|
36
36
|
if (subcommand === "discover") {
|
|
@@ -41,6 +41,11 @@ if (subcommand === "discover") {
|
|
|
41
41
|
}
|
|
42
42
|
const { runDiscover } = await import("./discover.js");
|
|
43
43
|
await runDiscover(filePath);
|
|
44
|
+
} else if (subcommand === "version") {
|
|
45
|
+
const { createRequire } = await import("module");
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
const { version } = require("../package.json");
|
|
48
|
+
process.stdout.write(version + "\n");
|
|
44
49
|
} else if (subcommand === "run") {
|
|
45
50
|
const [target, ...rest] = args;
|
|
46
51
|
if (!target) {
|
|
@@ -56,7 +61,15 @@ if (subcommand === "discover") {
|
|
|
56
61
|
}
|
|
57
62
|
const { runComponent } = await import("./run.js");
|
|
58
63
|
await runComponent(target, evalTarget);
|
|
64
|
+
} else if (subcommand === "eval-only") {
|
|
65
|
+
const [evalTarget] = args;
|
|
66
|
+
if (!evalTarget) {
|
|
67
|
+
console.error("Usage: zhanla-sdk-ts eval-only <file.ts:name>");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const { runEvalOnly } = await import("./run.js");
|
|
71
|
+
await runEvalOnly(evalTarget);
|
|
59
72
|
} else {
|
|
60
|
-
console.error(`Unknown subcommand: ${subcommand}. Use 'discover' or '
|
|
73
|
+
console.error(`Unknown subcommand: ${subcommand}. Use 'discover', 'run', or 'eval-only'.`);
|
|
61
74
|
process.exit(1);
|
|
62
75
|
}
|
package/bin/discover.js
CHANGED
|
@@ -11,7 +11,17 @@
|
|
|
11
11
|
|
|
12
12
|
import { pathToFileURL } from "url";
|
|
13
13
|
import { resolve } from "path";
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
export function splitFileSpec(fileSpec) {
|
|
16
|
+
const colonIndex = fileSpec.lastIndexOf(":");
|
|
17
|
+
if (colonIndex > 1) {
|
|
18
|
+
return {
|
|
19
|
+
filePath: fileSpec.slice(0, colonIndex),
|
|
20
|
+
symbolName: fileSpec.slice(colonIndex + 1) || null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { filePath: fileSpec, symbolName: null };
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
function isComponentLike(value) {
|
|
17
27
|
return (
|
|
@@ -216,7 +226,6 @@ export async function runDiscover(fileSpec) {
|
|
|
216
226
|
const { filePath, symbolName } = splitFileSpec(fileSpec);
|
|
217
227
|
|
|
218
228
|
const absolutePath = resolve(filePath);
|
|
219
|
-
loadDotenvLocal(absolutePath);
|
|
220
229
|
const fileUrl = pathToFileURL(absolutePath).href;
|
|
221
230
|
|
|
222
231
|
let moduleExports;
|
package/bin/run.js
CHANGED
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { pathToFileURL } from "url";
|
|
12
|
+
import { readFileSync } from "fs";
|
|
12
13
|
import { resolve } from "path";
|
|
14
|
+
import vm from "vm";
|
|
13
15
|
import readline from "readline";
|
|
14
|
-
import
|
|
16
|
+
import ts from "typescript";
|
|
15
17
|
|
|
16
18
|
function redirectConsoleToStderr() {
|
|
17
19
|
const stringifyArg = (arg) => {
|
|
@@ -55,7 +57,6 @@ async function loadTargetComponent(target, collectExportedComponents) {
|
|
|
55
57
|
const { filePath, symbolName } = parseTarget(target);
|
|
56
58
|
|
|
57
59
|
const absolutePath = resolve(filePath);
|
|
58
|
-
loadDotenvLocal(absolutePath);
|
|
59
60
|
const fileUrl = pathToFileURL(absolutePath).href;
|
|
60
61
|
|
|
61
62
|
let moduleExports;
|
|
@@ -78,6 +79,344 @@ async function loadTargetComponent(target, collectExportedComponents) {
|
|
|
78
79
|
return component;
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function isExportedStatement(statement) {
|
|
83
|
+
return statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getStringLiteralValue(node) {
|
|
87
|
+
if (!node) return null;
|
|
88
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
89
|
+
return node.text;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getManagedCodeEvalName(initializer) {
|
|
95
|
+
if (!initializer || !ts.isNewExpression(initializer)) return null;
|
|
96
|
+
if (!ts.isIdentifier(initializer.expression) || initializer.expression.text !== "CodeEval") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const [config] = initializer.arguments ?? [];
|
|
100
|
+
if (!config || !ts.isObjectLiteralExpression(config)) return null;
|
|
101
|
+
for (const property of config.properties) {
|
|
102
|
+
if (
|
|
103
|
+
ts.isPropertyAssignment(property) &&
|
|
104
|
+
ts.isIdentifier(property.name) &&
|
|
105
|
+
property.name.text === "name"
|
|
106
|
+
) {
|
|
107
|
+
return getStringLiteralValue(property.initializer);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildManagedTsIsolationContext(code) {
|
|
114
|
+
const sourceFile = ts.createSourceFile(
|
|
115
|
+
"managed-eval.ts",
|
|
116
|
+
code,
|
|
117
|
+
ts.ScriptTarget.ES2020,
|
|
118
|
+
true,
|
|
119
|
+
ts.ScriptKind.TS,
|
|
120
|
+
);
|
|
121
|
+
const topLevelDeclarations = new Map();
|
|
122
|
+
const importedBindings = new Map();
|
|
123
|
+
const sdkImportStatements = [];
|
|
124
|
+
|
|
125
|
+
for (const statement of sourceFile.statements) {
|
|
126
|
+
if (ts.isImportDeclaration(statement)) {
|
|
127
|
+
const moduleSpecifier = getStringLiteralValue(statement.moduleSpecifier);
|
|
128
|
+
if (moduleSpecifier === "@zhanla/sdk-ts") {
|
|
129
|
+
sdkImportStatements.push(statement);
|
|
130
|
+
}
|
|
131
|
+
const importClause = statement.importClause;
|
|
132
|
+
if (moduleSpecifier && importClause) {
|
|
133
|
+
if (importClause.name) {
|
|
134
|
+
importedBindings.set(importClause.name.text, moduleSpecifier);
|
|
135
|
+
}
|
|
136
|
+
const namedBindings = importClause.namedBindings;
|
|
137
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
138
|
+
for (const element of namedBindings.elements) {
|
|
139
|
+
importedBindings.set(element.name.text, moduleSpecifier);
|
|
140
|
+
}
|
|
141
|
+
} else if (namedBindings && ts.isNamespaceImport(namedBindings)) {
|
|
142
|
+
importedBindings.set(namedBindings.name.text, moduleSpecifier);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
ts.isFunctionDeclaration(statement) ||
|
|
150
|
+
ts.isClassDeclaration(statement) ||
|
|
151
|
+
ts.isEnumDeclaration(statement)
|
|
152
|
+
) {
|
|
153
|
+
if (statement.name) {
|
|
154
|
+
topLevelDeclarations.set(statement.name.text, statement);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (ts.isVariableStatement(statement)) {
|
|
160
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
161
|
+
if (ts.isIdentifier(declaration.name)) {
|
|
162
|
+
topLevelDeclarations.set(declaration.name.text, statement);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { sourceFile, topLevelDeclarations, importedBindings, sdkImportStatements };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function scanManagedTsDependencies(statement, context) {
|
|
172
|
+
const referencedDeclarations = new Set();
|
|
173
|
+
const disallowedImports = new Map();
|
|
174
|
+
|
|
175
|
+
const visit = (node) => {
|
|
176
|
+
if (ts.isIdentifier(node)) {
|
|
177
|
+
if (
|
|
178
|
+
(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) ||
|
|
179
|
+
(ts.isPropertyAssignment(node.parent) && node.parent.name === node)
|
|
180
|
+
) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const localStatement = context.topLevelDeclarations.get(node.text);
|
|
185
|
+
if (localStatement && localStatement !== statement) {
|
|
186
|
+
referencedDeclarations.add(localStatement);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const importedFrom = context.importedBindings.get(node.text);
|
|
190
|
+
if (importedFrom && importedFrom !== "@zhanla/sdk-ts") {
|
|
191
|
+
const bindings = disallowedImports.get(importedFrom) ?? new Set();
|
|
192
|
+
bindings.add(node.text);
|
|
193
|
+
disallowedImports.set(importedFrom, bindings);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
ts.forEachChild(node, visit);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
visit(statement);
|
|
201
|
+
return { referencedDeclarations, disallowedImports };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isolateManagedTypeScriptEvalSource(code, evalName) {
|
|
205
|
+
const context = buildManagedTsIsolationContext(code);
|
|
206
|
+
|
|
207
|
+
let targetStatement = null;
|
|
208
|
+
for (const statement of context.sourceFile.statements) {
|
|
209
|
+
if (!ts.isVariableStatement(statement) || !isExportedStatement(statement)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
213
|
+
if (!ts.isIdentifier(declaration.name)) continue;
|
|
214
|
+
const declaredEvalName = getManagedCodeEvalName(declaration.initializer);
|
|
215
|
+
if (declaredEvalName === evalName || declaration.name.text === evalName) {
|
|
216
|
+
targetStatement = statement;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (targetStatement) break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!targetStatement) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Managed TypeScript eval '${evalName}' was not found in the synced source file.`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const includedStatements = new Set([targetStatement]);
|
|
230
|
+
const pendingStatements = [targetStatement];
|
|
231
|
+
const disallowedImports = new Map();
|
|
232
|
+
|
|
233
|
+
while (pendingStatements.length > 0) {
|
|
234
|
+
const statement = pendingStatements.pop();
|
|
235
|
+
if (!statement) continue;
|
|
236
|
+
|
|
237
|
+
const scan = scanManagedTsDependencies(statement, context);
|
|
238
|
+
for (const dependency of scan.referencedDeclarations) {
|
|
239
|
+
if (!includedStatements.has(dependency)) {
|
|
240
|
+
includedStatements.add(dependency);
|
|
241
|
+
pendingStatements.push(dependency);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const [moduleSpecifier, bindings] of scan.disallowedImports) {
|
|
245
|
+
const existing = disallowedImports.get(moduleSpecifier) ?? new Set();
|
|
246
|
+
for (const binding of bindings) {
|
|
247
|
+
existing.add(binding);
|
|
248
|
+
}
|
|
249
|
+
disallowedImports.set(moduleSpecifier, existing);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (disallowedImports.size > 0) {
|
|
254
|
+
const formattedImports = Array.from(disallowedImports.entries())
|
|
255
|
+
.map(
|
|
256
|
+
([moduleSpecifier, bindings]) =>
|
|
257
|
+
`${moduleSpecifier} (${Array.from(bindings).sort().join(", ")})`,
|
|
258
|
+
)
|
|
259
|
+
.join("; ");
|
|
260
|
+
throw new Error(
|
|
261
|
+
`Managed TypeScript eval '${evalName}' uses unsupported imports: ${formattedImports}. ` +
|
|
262
|
+
"Keep managed evals self-contained and move helpers into the same file.",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return context.sourceFile.statements
|
|
267
|
+
.filter(
|
|
268
|
+
(statement) =>
|
|
269
|
+
context.sdkImportStatements.includes(statement) || includedStatements.has(statement),
|
|
270
|
+
)
|
|
271
|
+
.map((statement) => statement.getFullText(context.sourceFile))
|
|
272
|
+
.join("");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildEvalOnlySandbox(code) {
|
|
276
|
+
const transpiled = ts.transpileModule(code, {
|
|
277
|
+
compilerOptions: {
|
|
278
|
+
module: ts.ModuleKind.CommonJS,
|
|
279
|
+
target: ts.ScriptTarget.ES2020,
|
|
280
|
+
esModuleInterop: true,
|
|
281
|
+
},
|
|
282
|
+
}).outputText;
|
|
283
|
+
|
|
284
|
+
const moduleRecord = { exports: {} };
|
|
285
|
+
const sdkStub = class {
|
|
286
|
+
constructor(opts) {
|
|
287
|
+
Object.assign(this, opts);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const sandbox = vm.createContext({
|
|
292
|
+
JSON,
|
|
293
|
+
Math,
|
|
294
|
+
Array,
|
|
295
|
+
Object,
|
|
296
|
+
String,
|
|
297
|
+
Number,
|
|
298
|
+
Boolean,
|
|
299
|
+
Date,
|
|
300
|
+
RegExp,
|
|
301
|
+
Promise,
|
|
302
|
+
Error,
|
|
303
|
+
TypeError,
|
|
304
|
+
RangeError,
|
|
305
|
+
ReferenceError,
|
|
306
|
+
SyntaxError,
|
|
307
|
+
Map,
|
|
308
|
+
Set,
|
|
309
|
+
WeakMap,
|
|
310
|
+
WeakSet,
|
|
311
|
+
Symbol,
|
|
312
|
+
parseInt,
|
|
313
|
+
parseFloat,
|
|
314
|
+
isNaN,
|
|
315
|
+
isFinite,
|
|
316
|
+
encodeURIComponent,
|
|
317
|
+
decodeURIComponent,
|
|
318
|
+
console,
|
|
319
|
+
exports: moduleRecord.exports,
|
|
320
|
+
module: moduleRecord,
|
|
321
|
+
require: (specifier) => {
|
|
322
|
+
if (specifier === "@zhanla/sdk-ts") {
|
|
323
|
+
return {
|
|
324
|
+
CodeEval: sdkStub,
|
|
325
|
+
LLMEval: sdkStub,
|
|
326
|
+
Checklist: sdkStub,
|
|
327
|
+
EvalTree: sdkStub,
|
|
328
|
+
Branch: sdkStub,
|
|
329
|
+
Leaf: sdkStub,
|
|
330
|
+
Edge: sdkStub,
|
|
331
|
+
Tool: sdkStub,
|
|
332
|
+
Skill: sdkStub,
|
|
333
|
+
Agent: sdkStub,
|
|
334
|
+
Orchestration: sdkStub,
|
|
335
|
+
Step: sdkStub,
|
|
336
|
+
ComponentValue: null,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Cannot import '${specifier}' in the managed TypeScript eval sandbox. ` +
|
|
341
|
+
"Only @zhanla/sdk-ts imports and same-file helpers are supported.",
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
sandbox.globalThis = sandbox;
|
|
346
|
+
new vm.Script(transpiled).runInContext(sandbox, { timeout: 10000 });
|
|
347
|
+
return moduleRecord;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function findEvalComponent(moduleRecord, evalName) {
|
|
351
|
+
for (const value of Object.values(moduleRecord.exports)) {
|
|
352
|
+
if (value && typeof value === "object" && value.name === evalName && typeof value.fn === "function") {
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function loadIsolatedEvalComponent(target) {
|
|
360
|
+
const { filePath, symbolName } = parseTarget(target);
|
|
361
|
+
const absolutePath = resolve(filePath);
|
|
362
|
+
const sourceText = readFileSync(absolutePath, "utf8");
|
|
363
|
+
const isolatedSource = isolateManagedTypeScriptEvalSource(sourceText, symbolName);
|
|
364
|
+
const moduleRecord = buildEvalOnlySandbox(isolatedSource);
|
|
365
|
+
const component = findEvalComponent(moduleRecord, symbolName);
|
|
366
|
+
if (!component) {
|
|
367
|
+
throw new Error(`Component '${symbolName}' not found in isolated eval source.`);
|
|
368
|
+
}
|
|
369
|
+
return component;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export async function runEvalOnly(evalTarget) {
|
|
373
|
+
redirectConsoleToStderr();
|
|
374
|
+
let evalComponent;
|
|
375
|
+
try {
|
|
376
|
+
evalComponent = loadIsolatedEvalComponent(evalTarget);
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
379
|
+
process.exit(1);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rl = readline.createInterface({
|
|
383
|
+
input: process.stdin,
|
|
384
|
+
crlfDelay: Infinity,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
for await (const line of rl) {
|
|
388
|
+
const trimmed = line.trim();
|
|
389
|
+
if (!trimmed) continue;
|
|
390
|
+
|
|
391
|
+
let context;
|
|
392
|
+
try {
|
|
393
|
+
context = JSON.parse(trimmed);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
process.stdout.write(JSON.stringify({ status: "error", error: `Invalid JSON: ${err.message}` }) + "\n");
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
const evalOutput = evalComponent.fn.length <= 1
|
|
401
|
+
? await Promise.resolve(evalComponent.fn(context))
|
|
402
|
+
: await Promise.resolve(
|
|
403
|
+
evalComponent.fn(
|
|
404
|
+
context.model_response,
|
|
405
|
+
context.expected_output,
|
|
406
|
+
context.model_input,
|
|
407
|
+
),
|
|
408
|
+
);
|
|
409
|
+
process.stdout.write(
|
|
410
|
+
JSON.stringify({ status: "ok", name: evalComponent.name, eval_output: evalOutput }) + "\n"
|
|
411
|
+
);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
process.stdout.write(
|
|
414
|
+
JSON.stringify({ status: "error", name: evalComponent.name, error: err instanceof Error ? err.message : String(err) }) + "\n"
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
81
420
|
export async function runComponent(target, evalTarget = null) {
|
|
82
421
|
redirectConsoleToStderr();
|
|
83
422
|
// Dynamically import from SDK
|
package/dist/executor.d.ts
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* Mirrors the Python CLI executor's component-type dispatch semantics.
|
|
4
4
|
*/
|
|
5
5
|
import { ZhanlaComponent } from "./types.js";
|
|
6
|
+
import { LLMCall } from "./trace_store.js";
|
|
6
7
|
export interface RowResult {
|
|
7
8
|
status: "ok" | "error";
|
|
8
9
|
output?: Record<string, unknown>;
|
|
9
10
|
error?: string;
|
|
10
11
|
pathTaken?: Record<string, unknown>;
|
|
12
|
+
componentLlmCalls?: LLMCall[];
|
|
11
13
|
}
|
|
12
14
|
export declare function executeComponent(comp: ZhanlaComponent, kwargs: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
13
15
|
export declare function executeRow(component: ZhanlaComponent, evalComponent: ZhanlaComponent | null, row: Record<string, unknown>): Promise<RowResult>;
|
package/dist/executor.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Local execution engine for TypeScript SDK components.
|
|
3
3
|
* Mirrors the Python CLI executor's component-type dispatch semantics.
|
|
4
4
|
*/
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
5
6
|
import { parseJsonResponse } from "./json.js";
|
|
7
|
+
import { TraceContext, traceStorage } from "./trace_store.js";
|
|
6
8
|
const COMPONENT_ERROR_KEY = "_component_error";
|
|
7
9
|
const OUTPUT_SCHEMA_MISMATCH_REASON = "Model response doesn't match the requested output schema.";
|
|
8
10
|
const JSON_OUTPUT_ERROR_MARKERS = [
|
|
@@ -69,6 +71,12 @@ function stringifyValue(value) {
|
|
|
69
71
|
return String(value);
|
|
70
72
|
}
|
|
71
73
|
}
|
|
74
|
+
function deriveModelInput(row) {
|
|
75
|
+
if ("input" in row) {
|
|
76
|
+
return stringifyValue(row["input"]);
|
|
77
|
+
}
|
|
78
|
+
return stringifyValue(row);
|
|
79
|
+
}
|
|
72
80
|
function deriveExpectedOutput(row) {
|
|
73
81
|
if ("expected_output" in row) {
|
|
74
82
|
return stringifyValue(row["expected_output"]);
|
|
@@ -76,9 +84,12 @@ function deriveExpectedOutput(row) {
|
|
|
76
84
|
if ("output" in row) {
|
|
77
85
|
return stringifyValue(row["output"]);
|
|
78
86
|
}
|
|
79
|
-
return
|
|
87
|
+
return stringifyValue(row);
|
|
80
88
|
}
|
|
81
89
|
function deriveModelResponse(output) {
|
|
90
|
+
if (output == null) {
|
|
91
|
+
return stringifyValue(output);
|
|
92
|
+
}
|
|
82
93
|
const keys = Object.keys(output);
|
|
83
94
|
if (keys.length === 1 && keys[0] === "result") {
|
|
84
95
|
return stringifyValue(output["result"]);
|
|
@@ -88,6 +99,13 @@ function deriveModelResponse(output) {
|
|
|
88
99
|
}
|
|
89
100
|
return stringifyValue(output);
|
|
90
101
|
}
|
|
102
|
+
function buildCodeEvalKwargs(output, row) {
|
|
103
|
+
return {
|
|
104
|
+
model_input: deriveModelInput(row),
|
|
105
|
+
model_response: deriveModelResponse(output),
|
|
106
|
+
expected_output: deriveExpectedOutput(row),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
91
109
|
// ---------------------------------------------------------------------------
|
|
92
110
|
// Component execution dispatch
|
|
93
111
|
// ---------------------------------------------------------------------------
|
|
@@ -156,13 +174,11 @@ async function runTool(comp, kwargs) {
|
|
|
156
174
|
return { result };
|
|
157
175
|
}
|
|
158
176
|
async function runCodeEval(comp, kwargs) {
|
|
159
|
-
const modelResponse = kwargs["model_response"];
|
|
160
|
-
const expectedResponse = kwargs["expected_response"] ?? null;
|
|
161
177
|
const { value: result, logs } = await captureConsoleLogs(async () => {
|
|
162
178
|
if (comp.isAsync) {
|
|
163
|
-
return await comp.fn(
|
|
179
|
+
return await comp.fn(kwargs);
|
|
164
180
|
}
|
|
165
|
-
return comp.fn(
|
|
181
|
+
return comp.fn(kwargs);
|
|
166
182
|
});
|
|
167
183
|
if (result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
168
184
|
return logs.length > 0
|
|
@@ -528,14 +544,19 @@ export async function executeRow(component, evalComponent, row) {
|
|
|
528
544
|
let error;
|
|
529
545
|
let demotedError;
|
|
530
546
|
let orchestrationSteps;
|
|
547
|
+
let componentLlmCalls;
|
|
548
|
+
let componentFailedWithoutEval = false;
|
|
549
|
+
const traceContext = new TraceContext(randomUUID());
|
|
531
550
|
try {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
551
|
+
await traceStorage.run(traceContext, async () => {
|
|
552
|
+
if (component.componentType === "orchestration") {
|
|
553
|
+
orchestrationSteps = [];
|
|
554
|
+
output = await runOrchestration(component, row, orchestrationSteps);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
output = await executeComponent(component, row);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
539
560
|
}
|
|
540
561
|
catch (err) {
|
|
541
562
|
error = err instanceof Error ? err.message : String(err);
|
|
@@ -545,9 +566,20 @@ export async function executeRow(component, evalComponent, row) {
|
|
|
545
566
|
error = undefined;
|
|
546
567
|
}
|
|
547
568
|
else {
|
|
548
|
-
|
|
569
|
+
componentFailedWithoutEval = true;
|
|
549
570
|
}
|
|
550
571
|
}
|
|
572
|
+
finally {
|
|
573
|
+
componentLlmCalls = traceContext.flush();
|
|
574
|
+
}
|
|
575
|
+
if (componentFailedWithoutEval) {
|
|
576
|
+
return {
|
|
577
|
+
status: "error",
|
|
578
|
+
error,
|
|
579
|
+
pathTaken: buildPathTaken(component, row, undefined, error, orchestrationSteps),
|
|
580
|
+
componentLlmCalls,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
551
583
|
if (evalComponent != null) {
|
|
552
584
|
if (output != null
|
|
553
585
|
&& typeof output[COMPONENT_ERROR_KEY] === "string"
|
|
@@ -556,18 +588,17 @@ export async function executeRow(component, evalComponent, row) {
|
|
|
556
588
|
status: "ok",
|
|
557
589
|
output: { ...output, _eval: buildSchemaMismatchEvalOutput(String(output[COMPONENT_ERROR_KEY])) },
|
|
558
590
|
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
591
|
+
componentLlmCalls,
|
|
559
592
|
};
|
|
560
593
|
}
|
|
561
|
-
const evalKwargs =
|
|
562
|
-
model_response: deriveModelResponse(output),
|
|
563
|
-
expected_response: deriveExpectedOutput(row) || null,
|
|
564
|
-
};
|
|
594
|
+
const evalKwargs = buildCodeEvalKwargs(output, row);
|
|
565
595
|
try {
|
|
566
596
|
const evalOutput = await executeComponent(evalComponent, evalKwargs);
|
|
567
597
|
return {
|
|
568
598
|
status: "ok",
|
|
569
599
|
output: { ...output, _eval: evalOutput },
|
|
570
600
|
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
601
|
+
componentLlmCalls,
|
|
571
602
|
};
|
|
572
603
|
}
|
|
573
604
|
catch (err) {
|
|
@@ -577,6 +608,7 @@ export async function executeRow(component, evalComponent, row) {
|
|
|
577
608
|
output,
|
|
578
609
|
error: evalError,
|
|
579
610
|
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
611
|
+
componentLlmCalls,
|
|
580
612
|
};
|
|
581
613
|
}
|
|
582
614
|
}
|
|
@@ -584,6 +616,7 @@ export async function executeRow(component, evalComponent, row) {
|
|
|
584
616
|
status: "ok",
|
|
585
617
|
output,
|
|
586
618
|
pathTaken: buildPathTaken(component, row, output, demotedError, orchestrationSteps),
|
|
619
|
+
componentLlmCalls,
|
|
587
620
|
};
|
|
588
621
|
}
|
|
589
622
|
function buildPathTaken(component, inputData, output, error, orchestrationSteps) {
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Import component classes to define zhanla components in TypeScript.
|
|
4
4
|
*/
|
|
5
5
|
export { Tool, CodeEval, Skill, Agent, LLMProcessor, LLMEval, Runner, Conditional, Step, Orchestration, Leaf, Edge, Branch, Checklist, EvalTree, ZhanlaComponent, RUNNABLE_TYPES, EVAL_TYPES, } from "./types.js";
|
|
6
|
-
export type { ComponentValue, ToolOptions, CodeEvalOptions, SkillOptions, AgentOptions, LLMProcessorOptions, LLMEvalOptions, OrchestrationOptions, ChecklistOptions, EvalTreeOptions, StepOptions, ComponentType, JsonSchema, JsonSchemaObject, JsonSchemaArray, JsonSchemaScalar, ToolCall, LLMResponse, RunnerOptions, RunnerCallOptions, RunnerMessage, } from "./types.js";
|
|
6
|
+
export type { ComponentValue, ToolOptions, CodeEvalKwargs, CodeEvalOptions, SkillOptions, AgentOptions, LLMProcessorOptions, LLMEvalOptions, OrchestrationOptions, ChecklistOptions, EvalTreeOptions, StepOptions, ComponentType, JsonSchema, JsonSchemaObject, JsonSchemaArray, JsonSchemaScalar, ToolCall, LLMResponse, RunnerOptions, RunnerCallOptions, RunnerMessage, } from "./types.js";
|
|
7
7
|
export { toManifest, collectExportedComponents } from "./manifest.js";
|
|
8
8
|
export type { ComponentManifest, StepManifest, BranchManifest, EdgeManifest, LeafManifest } from "./manifest.js";
|
|
9
9
|
export { executeComponent, executeRow } from "./executor.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -57,10 +57,8 @@ export interface ComponentManifest {
|
|
|
57
57
|
output_schema?: JsonSchema;
|
|
58
58
|
file_path?: string;
|
|
59
59
|
symbol_name?: string;
|
|
60
|
-
source_code?: string;
|
|
61
|
-
source_language?: string;
|
|
62
|
-
source_format?: string;
|
|
63
60
|
model_response_format?: string;
|
|
61
|
+
questions?: string[];
|
|
64
62
|
}
|
|
65
63
|
export declare function toManifest(component: ZhanlaComponent, opts?: {
|
|
66
64
|
filePath?: string;
|
package/dist/manifest.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Manifest emission — serialize SDK component instances into ComponentManifest objects.
|
|
3
3
|
* The manifest shape mirrors the Python CLI's ComponentManifest dataclass.
|
|
4
4
|
*/
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
5
|
// ---------------------------------------------------------------------------
|
|
7
6
|
// Tree serialization helpers
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
@@ -54,87 +53,6 @@ function serializeBranch(branch) {
|
|
|
54
53
|
if_fail: branch.ifFail.map(serializeEdge),
|
|
55
54
|
};
|
|
56
55
|
}
|
|
57
|
-
function escapeRegExp(value) {
|
|
58
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
59
|
-
}
|
|
60
|
-
function findMatchingParen(source, startIndex) {
|
|
61
|
-
let depth = 0;
|
|
62
|
-
let quote = null;
|
|
63
|
-
let inLineComment = false;
|
|
64
|
-
let inBlockComment = false;
|
|
65
|
-
for (let i = startIndex; i < source.length; i += 1) {
|
|
66
|
-
const char = source[i];
|
|
67
|
-
const next = source[i + 1];
|
|
68
|
-
const prev = source[i - 1];
|
|
69
|
-
if (inLineComment) {
|
|
70
|
-
if (char === "\n")
|
|
71
|
-
inLineComment = false;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (inBlockComment) {
|
|
75
|
-
if (prev === "*" && char === "/")
|
|
76
|
-
inBlockComment = false;
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (quote) {
|
|
80
|
-
if (char === quote && prev !== "\\")
|
|
81
|
-
quote = null;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (char === "/" && next === "/") {
|
|
85
|
-
inLineComment = true;
|
|
86
|
-
i += 1;
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (char === "/" && next === "*") {
|
|
90
|
-
inBlockComment = true;
|
|
91
|
-
i += 1;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (char === "'" || char === '"' || char === "`") {
|
|
95
|
-
quote = char;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (char === "(") {
|
|
99
|
-
depth += 1;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (char === ")") {
|
|
103
|
-
depth -= 1;
|
|
104
|
-
if (depth === 0)
|
|
105
|
-
return i;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return -1;
|
|
109
|
-
}
|
|
110
|
-
function extractExportedComponentSource(filePath, symbolName) {
|
|
111
|
-
if (!filePath || !symbolName)
|
|
112
|
-
return undefined;
|
|
113
|
-
let source = "";
|
|
114
|
-
try {
|
|
115
|
-
source = readFileSync(filePath, "utf8");
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
const pattern = new RegExp(String.raw `(?:^|\n)\s*export\s+(?:const|let|var)\s+${escapeRegExp(symbolName)}\s*=\s*new\s+[A-Za-z_$][\w$.]*\s*\(`, "m");
|
|
121
|
-
const match = pattern.exec(source);
|
|
122
|
-
if (!match || match.index == null)
|
|
123
|
-
return undefined;
|
|
124
|
-
const start = source.lastIndexOf("export", match.index + match[0].length);
|
|
125
|
-
const openParenIndex = source.indexOf("(", match.index + match[0].length - 1);
|
|
126
|
-
if (start < 0 || openParenIndex < 0)
|
|
127
|
-
return undefined;
|
|
128
|
-
const closeParenIndex = findMatchingParen(source, openParenIndex);
|
|
129
|
-
if (closeParenIndex < 0)
|
|
130
|
-
return undefined;
|
|
131
|
-
let end = closeParenIndex + 1;
|
|
132
|
-
while (end < source.length && /\s/.test(source[end]))
|
|
133
|
-
end += 1;
|
|
134
|
-
if (source[end] === ";")
|
|
135
|
-
end += 1;
|
|
136
|
-
return source.slice(start, end).trim();
|
|
137
|
-
}
|
|
138
56
|
// ---------------------------------------------------------------------------
|
|
139
57
|
// to_manifest
|
|
140
58
|
// ---------------------------------------------------------------------------
|
|
@@ -150,7 +68,6 @@ const EXECUTION_MODES = {
|
|
|
150
68
|
eval_tree: "composite",
|
|
151
69
|
};
|
|
152
70
|
export function toManifest(component, opts = {}) {
|
|
153
|
-
const componentSource = extractExportedComponentSource(opts.filePath, opts.symbolName);
|
|
154
71
|
const key = component.key;
|
|
155
72
|
const keySource = "explicit";
|
|
156
73
|
const executionMode = EXECUTION_MODES[component.componentType] ?? "unknown";
|
|
@@ -175,9 +92,6 @@ export function toManifest(component, opts = {}) {
|
|
|
175
92
|
fn_present: true,
|
|
176
93
|
is_async: tool.isAsync,
|
|
177
94
|
output_schema: tool.outputSchema,
|
|
178
|
-
source_code: componentSource || tool.fn.toString(),
|
|
179
|
-
source_language: "typescript",
|
|
180
|
-
source_format: componentSource ? "component" : "function",
|
|
181
95
|
};
|
|
182
96
|
}
|
|
183
97
|
case "code_eval": {
|
|
@@ -186,9 +100,6 @@ export function toManifest(component, opts = {}) {
|
|
|
186
100
|
...base,
|
|
187
101
|
fn_present: true,
|
|
188
102
|
is_async: codeEval.isAsync,
|
|
189
|
-
source_code: componentSource || codeEval.fn.toString(),
|
|
190
|
-
source_language: "typescript",
|
|
191
|
-
source_format: componentSource ? "component" : "function",
|
|
192
103
|
model_response_format: codeEval.modelResponseFormat,
|
|
193
104
|
};
|
|
194
105
|
}
|
|
@@ -201,9 +112,6 @@ export function toManifest(component, opts = {}) {
|
|
|
201
112
|
is_async: skill.isAsync,
|
|
202
113
|
tool_refs: skill.tools.length > 0 ? skill.tools.map(resolveComponentRef) : undefined,
|
|
203
114
|
output_schema: skill.outputSchema,
|
|
204
|
-
source_code: componentSource || skill.fn?.toString(),
|
|
205
|
-
source_language: "typescript",
|
|
206
|
-
source_format: componentSource ? "component" : skill.fn ? "function" : undefined,
|
|
207
115
|
};
|
|
208
116
|
}
|
|
209
117
|
case "agent": {
|
|
@@ -233,7 +141,8 @@ export function toManifest(component, opts = {}) {
|
|
|
233
141
|
const llmEval = component;
|
|
234
142
|
return {
|
|
235
143
|
...base,
|
|
236
|
-
instructions: llmEval.instructions,
|
|
144
|
+
...(llmEval.instructions !== undefined ? { instructions: llmEval.instructions } : {}),
|
|
145
|
+
...(llmEval.questions !== undefined ? { questions: llmEval.questions } : {}),
|
|
237
146
|
model: llmEval.model,
|
|
238
147
|
temperature: llmEval.temperature,
|
|
239
148
|
output_schema: llmEval.outputSchema,
|
package/dist/trace_store.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface LLMCall {
|
|
|
12
12
|
sequenceOrder: number;
|
|
13
13
|
autoraterRunId: string | null;
|
|
14
14
|
datasetItemId: string | null;
|
|
15
|
-
provider: "anthropic" | "openai" | "gemini";
|
|
15
|
+
provider: "anthropic" | "openai" | "gemini" | "openrouter";
|
|
16
16
|
model: string;
|
|
17
17
|
inputMessages: unknown[];
|
|
18
18
|
output: unknown | null;
|
package/dist/types.d.ts
CHANGED
|
@@ -103,10 +103,15 @@ export declare class Tool extends ZhanlaComponent {
|
|
|
103
103
|
readonly key: string;
|
|
104
104
|
constructor(opts: ToolOptions);
|
|
105
105
|
}
|
|
106
|
+
export interface CodeEvalKwargs extends Record<string, unknown> {
|
|
107
|
+
model_input?: string;
|
|
108
|
+
model_response: string;
|
|
109
|
+
expected_output?: string;
|
|
110
|
+
}
|
|
106
111
|
export interface CodeEvalOptions {
|
|
107
112
|
name: string;
|
|
108
113
|
description: string;
|
|
109
|
-
fn: (
|
|
114
|
+
fn: (kwargs: CodeEvalKwargs) => unknown;
|
|
110
115
|
modelResponseFormat?: "JSON" | "TEXT" | "YAML";
|
|
111
116
|
key: string;
|
|
112
117
|
}
|
|
@@ -114,7 +119,7 @@ export declare class CodeEval extends ZhanlaComponent {
|
|
|
114
119
|
readonly componentType: "code_eval";
|
|
115
120
|
readonly name: string;
|
|
116
121
|
readonly description: string;
|
|
117
|
-
readonly fn: (
|
|
122
|
+
readonly fn: (kwargs: CodeEvalKwargs) => unknown;
|
|
118
123
|
readonly isAsync: boolean;
|
|
119
124
|
readonly modelResponseFormat: "JSON" | "TEXT" | "YAML";
|
|
120
125
|
readonly key: string;
|
|
@@ -204,7 +209,8 @@ export declare class LLMProcessor extends ZhanlaComponent {
|
|
|
204
209
|
export interface LLMEvalOptions {
|
|
205
210
|
name: string;
|
|
206
211
|
description: string;
|
|
207
|
-
instructions
|
|
212
|
+
instructions?: string;
|
|
213
|
+
questions?: string[];
|
|
208
214
|
model: string;
|
|
209
215
|
client?: unknown;
|
|
210
216
|
runner?: Runner;
|
|
@@ -218,7 +224,8 @@ export declare class LLMEval extends ZhanlaComponent {
|
|
|
218
224
|
readonly componentType: "llm_eval";
|
|
219
225
|
readonly name: string;
|
|
220
226
|
readonly description: string;
|
|
221
|
-
readonly instructions
|
|
227
|
+
readonly instructions?: string;
|
|
228
|
+
readonly questions?: string[];
|
|
222
229
|
readonly model: string;
|
|
223
230
|
readonly runner?: Runner;
|
|
224
231
|
readonly outputSchema?: JsonSchema;
|
package/dist/types.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Mirrors the Python SDK's class-based component model.
|
|
4
4
|
*/
|
|
5
5
|
import { parseJsonResponse } from "./json.js";
|
|
6
|
-
import { isAnthropicClient, isGeminiClient, isOpenAIClient, wrap } from "./wrap.js";
|
|
6
|
+
import { isAnthropicClient, isGeminiClient, isOpenAIClient, isOpenRouterClient, wrap } from "./wrap.js";
|
|
7
7
|
export const RUNNABLE_TYPES = new Set([
|
|
8
8
|
"tool",
|
|
9
9
|
"agent",
|
|
@@ -210,6 +210,8 @@ export class Runner {
|
|
|
210
210
|
providerName() {
|
|
211
211
|
if (isAnthropicClient(this.client))
|
|
212
212
|
return "anthropic";
|
|
213
|
+
if (isOpenRouterClient(this.client))
|
|
214
|
+
return "openrouter";
|
|
213
215
|
if (isOpenAIClient(this.client))
|
|
214
216
|
return "openai";
|
|
215
217
|
if (isGeminiClient(this.client))
|
|
@@ -583,6 +585,7 @@ export class LLMEval extends ZhanlaComponent {
|
|
|
583
585
|
name;
|
|
584
586
|
description;
|
|
585
587
|
instructions;
|
|
588
|
+
questions;
|
|
586
589
|
model;
|
|
587
590
|
runner;
|
|
588
591
|
outputSchema;
|
|
@@ -592,12 +595,21 @@ export class LLMEval extends ZhanlaComponent {
|
|
|
592
595
|
key;
|
|
593
596
|
constructor(opts) {
|
|
594
597
|
super();
|
|
598
|
+
const hasInstructions = opts.instructions !== undefined && opts.instructions !== "";
|
|
599
|
+
const hasQuestions = opts.questions !== undefined && opts.questions.length > 0;
|
|
600
|
+
if (hasInstructions && hasQuestions) {
|
|
601
|
+
throw new Error(`LLMEval '${opts.name}': provide exactly one of 'instructions' or 'questions', not both.`);
|
|
602
|
+
}
|
|
603
|
+
if (!hasInstructions && !hasQuestions) {
|
|
604
|
+
throw new Error(`LLMEval '${opts.name}': must provide exactly one of 'instructions' or 'questions'.`);
|
|
605
|
+
}
|
|
595
606
|
if (opts.client !== undefined && opts.runner !== undefined) {
|
|
596
607
|
throw new Error("Specify client or runner, not both.");
|
|
597
608
|
}
|
|
598
609
|
this.name = opts.name;
|
|
599
610
|
this.description = opts.description;
|
|
600
611
|
this.instructions = opts.instructions;
|
|
612
|
+
this.questions = opts.questions;
|
|
601
613
|
this.model = opts.model;
|
|
602
614
|
this.runner = opts.client !== undefined ? new Runner({ client: opts.client }) : opts.runner;
|
|
603
615
|
this.outputSchema = opts.outputSchema;
|
package/dist/wrap.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Supported clients:
|
|
8
8
|
* - Anthropic (from @anthropic-ai/sdk)
|
|
9
9
|
* - OpenAI (from openai)
|
|
10
|
+
* - OpenRouter (OpenAI client with baseURL containing "openrouter")
|
|
10
11
|
* - GoogleGenAI (from @google/genai)
|
|
11
12
|
*/
|
|
12
13
|
export declare function isAnthropicClient(client: unknown): client is {
|
|
@@ -21,6 +22,14 @@ export declare function isOpenAIClient(client: unknown): client is {
|
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
24
|
};
|
|
25
|
+
export declare function isOpenRouterClient(client: unknown): client is {
|
|
26
|
+
chat: {
|
|
27
|
+
completions: {
|
|
28
|
+
create: (...args: unknown[]) => unknown;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
baseURL: string;
|
|
32
|
+
};
|
|
24
33
|
export declare function isGeminiClient(client: unknown): client is {
|
|
25
34
|
models: {
|
|
26
35
|
generateContent: (...args: unknown[]) => unknown;
|
package/dist/wrap.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Supported clients:
|
|
8
8
|
* - Anthropic (from @anthropic-ai/sdk)
|
|
9
9
|
* - OpenAI (from openai)
|
|
10
|
+
* - OpenRouter (OpenAI client with baseURL containing "openrouter")
|
|
10
11
|
* - GoogleGenAI (from @google/genai)
|
|
11
12
|
*/
|
|
12
13
|
import { traceStorage } from "./trace_store.js";
|
|
@@ -104,7 +105,7 @@ function wrapAnthropic(client) {
|
|
|
104
105
|
// ---------------------------------------------------------------------------
|
|
105
106
|
// OpenAI
|
|
106
107
|
// ---------------------------------------------------------------------------
|
|
107
|
-
function recordOpenAICall(ctx, kwargs, response, latencyMs) {
|
|
108
|
+
function recordOpenAICall(ctx, kwargs, response, latencyMs, provider = "openai") {
|
|
108
109
|
if (!ctx)
|
|
109
110
|
return;
|
|
110
111
|
const choices = response["choices"] ?? [];
|
|
@@ -119,7 +120,7 @@ function recordOpenAICall(ctx, kwargs, response, latencyMs) {
|
|
|
119
120
|
sequenceOrder: ctx.nextSequence(),
|
|
120
121
|
autoraterRunId: ctx.autoraterRunId,
|
|
121
122
|
datasetItemId: ctx.datasetItemId,
|
|
122
|
-
provider
|
|
123
|
+
provider,
|
|
123
124
|
model: String(response["model"] ?? (kwargs["model"] ?? "")),
|
|
124
125
|
inputMessages: serialize(kwargs["messages"] ?? []),
|
|
125
126
|
output: serialize(message),
|
|
@@ -132,7 +133,7 @@ function recordOpenAICall(ctx, kwargs, response, latencyMs) {
|
|
|
132
133
|
metadata: null,
|
|
133
134
|
});
|
|
134
135
|
}
|
|
135
|
-
function wrapOpenAI(client) {
|
|
136
|
+
function wrapOpenAI(client, provider = "openai") {
|
|
136
137
|
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
137
138
|
client.chat.completions.create = async function (...args) {
|
|
138
139
|
const ctx = traceStorage.getStore();
|
|
@@ -140,7 +141,7 @@ function wrapOpenAI(client) {
|
|
|
140
141
|
const start = performance.now();
|
|
141
142
|
const response = await originalCreate(...args);
|
|
142
143
|
const latencyMs = Math.round(performance.now() - start);
|
|
143
|
-
recordOpenAICall(ctx, kwargs, response, latencyMs);
|
|
144
|
+
recordOpenAICall(ctx, kwargs, response, latencyMs, provider);
|
|
144
145
|
return response;
|
|
145
146
|
};
|
|
146
147
|
return client;
|
|
@@ -208,6 +209,12 @@ export function isOpenAIClient(client) {
|
|
|
208
209
|
client.chat !== null &&
|
|
209
210
|
typeof client.chat.completions === "object");
|
|
210
211
|
}
|
|
212
|
+
export function isOpenRouterClient(client) {
|
|
213
|
+
if (!isOpenAIClient(client))
|
|
214
|
+
return false;
|
|
215
|
+
const baseURL = client["baseURL"];
|
|
216
|
+
return typeof baseURL === "string" && baseURL.includes("openrouter");
|
|
217
|
+
}
|
|
211
218
|
export function isGeminiClient(client) {
|
|
212
219
|
return (client !== null &&
|
|
213
220
|
typeof client === "object" &&
|
|
@@ -235,13 +242,15 @@ export function wrap(client) {
|
|
|
235
242
|
let wrapped;
|
|
236
243
|
if (isAnthropicClient(client))
|
|
237
244
|
wrapped = wrapAnthropic(client);
|
|
245
|
+
else if (isOpenRouterClient(client))
|
|
246
|
+
wrapped = wrapOpenAI(client, "openrouter");
|
|
238
247
|
else if (isOpenAIClient(client))
|
|
239
248
|
wrapped = wrapOpenAI(client);
|
|
240
249
|
else if (isGeminiClient(client))
|
|
241
250
|
wrapped = wrapGemini(client);
|
|
242
251
|
else {
|
|
243
252
|
throw new TypeError(`bench.wrap() does not support ${Object.getPrototypeOf(client)?.constructor?.name ?? typeof client}. ` +
|
|
244
|
-
"Supported clients: Anthropic, OpenAI, GoogleGenAI.");
|
|
253
|
+
"Supported clients: Anthropic, OpenAI, OpenRouter (OpenAI client with OpenRouter baseURL), GoogleGenAI.");
|
|
245
254
|
}
|
|
246
255
|
if (wrapped != null && typeof wrapped === "object") {
|
|
247
256
|
Object.defineProperty(wrapped, WRAPPED_CLIENT, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhanla/sdk-ts",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "TypeScript SDK for the zhanla CLI — define and run AI components locally",
|
|
5
5
|
"homepage": "https://benchmark-black.vercel.app/",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"postinstall": "node ./bin/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"tsx": "^4.7.0"
|
|
23
|
+
"tsx": "^4.7.0",
|
|
24
|
+
"typescript": "^5.4.0"
|
|
24
25
|
},
|
|
25
26
|
"files": [
|
|
26
27
|
"dist",
|
package/bin/env.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { existsSync, readFileSync, statSync } from "fs";
|
|
4
|
-
import { dirname, join, resolve } from "path";
|
|
5
|
-
|
|
6
|
-
function isDirectory(path) {
|
|
7
|
-
try {
|
|
8
|
-
return statSync(path).isDirectory();
|
|
9
|
-
} catch {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function splitFileSpec(fileSpec) {
|
|
15
|
-
const colonIndex = fileSpec.lastIndexOf(":");
|
|
16
|
-
// Preserve Windows drive letters (e.g. C:\...) by only splitting on later colons.
|
|
17
|
-
if (colonIndex > 1) {
|
|
18
|
-
return {
|
|
19
|
-
filePath: fileSpec.slice(0, colonIndex),
|
|
20
|
-
symbolName: fileSpec.slice(colonIndex + 1) || null,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
return { filePath: fileSpec, symbolName: null };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function findDotenvLocal(startPath) {
|
|
27
|
-
let current = resolve(startPath);
|
|
28
|
-
if (!isDirectory(current)) {
|
|
29
|
-
current = dirname(current);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
while (true) {
|
|
33
|
-
const candidate = join(current, ".env.local");
|
|
34
|
-
if (existsSync(candidate)) {
|
|
35
|
-
return candidate;
|
|
36
|
-
}
|
|
37
|
-
const parent = dirname(current);
|
|
38
|
-
if (parent === current) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
current = parent;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function loadDotenvLocal(startPath) {
|
|
46
|
-
const envPath = findDotenvLocal(startPath);
|
|
47
|
-
if (!envPath) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const source = readFileSync(envPath, "utf8");
|
|
52
|
-
for (const line of source.split(/\r?\n/)) {
|
|
53
|
-
const trimmed = line.trim();
|
|
54
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
55
|
-
const equalsIndex = trimmed.indexOf("=");
|
|
56
|
-
if (equalsIndex === -1) continue;
|
|
57
|
-
|
|
58
|
-
const key = trimmed.slice(0, equalsIndex).trim();
|
|
59
|
-
let value = trimmed.slice(equalsIndex + 1).trim();
|
|
60
|
-
if (
|
|
61
|
-
(value.startsWith('"') && value.endsWith('"'))
|
|
62
|
-
|| (value.startsWith("'") && value.endsWith("'"))
|
|
63
|
-
) {
|
|
64
|
-
value = value.slice(1, -1);
|
|
65
|
-
}
|
|
66
|
-
if (key && process.env[key] === undefined) {
|
|
67
|
-
process.env[key] = value;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return envPath;
|
|
72
|
-
}
|