@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.
@@ -0,0 +1,275 @@
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 { readFileSync } from "node:fs";
6
+ // ---------------------------------------------------------------------------
7
+ // Tree serialization helpers
8
+ // ---------------------------------------------------------------------------
9
+ function isComponentLike(value) {
10
+ return (value != null &&
11
+ typeof value === "object" &&
12
+ typeof value.componentType === "string" &&
13
+ typeof value.name === "string" &&
14
+ typeof value.description === "string" &&
15
+ typeof value.versionHash === "function");
16
+ }
17
+ function isConditionalLike(value) {
18
+ return (value != null &&
19
+ typeof value === "object" &&
20
+ typeof value.condition === "function" &&
21
+ typeof value.ifTrue === "string" &&
22
+ typeof value.ifFalse === "string");
23
+ }
24
+ function isLeafLike(value) {
25
+ return (value != null &&
26
+ typeof value === "object" &&
27
+ "eval" in value &&
28
+ !("threshold" in value));
29
+ }
30
+ function serializeEdge(edge) {
31
+ return {
32
+ weight: edge.weight,
33
+ node: isLeafLike(edge.node)
34
+ ? { type: "leaf", eval: edge.node.eval.name }
35
+ : serializeBranch(edge.node),
36
+ };
37
+ }
38
+ function serializeBranch(branch) {
39
+ return {
40
+ type: "branch",
41
+ eval: branch.eval.name,
42
+ threshold: branch.threshold,
43
+ if_pass: branch.ifPass.map(serializeEdge),
44
+ if_fail: branch.ifFail.map(serializeEdge),
45
+ };
46
+ }
47
+ function escapeRegExp(value) {
48
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49
+ }
50
+ function findMatchingParen(source, startIndex) {
51
+ let depth = 0;
52
+ let quote = null;
53
+ let inLineComment = false;
54
+ let inBlockComment = false;
55
+ for (let i = startIndex; i < source.length; i += 1) {
56
+ const char = source[i];
57
+ const next = source[i + 1];
58
+ const prev = source[i - 1];
59
+ if (inLineComment) {
60
+ if (char === "\n")
61
+ inLineComment = false;
62
+ continue;
63
+ }
64
+ if (inBlockComment) {
65
+ if (prev === "*" && char === "/")
66
+ inBlockComment = false;
67
+ continue;
68
+ }
69
+ if (quote) {
70
+ if (char === quote && prev !== "\\")
71
+ quote = null;
72
+ continue;
73
+ }
74
+ if (char === "/" && next === "/") {
75
+ inLineComment = true;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (char === "/" && next === "*") {
80
+ inBlockComment = true;
81
+ i += 1;
82
+ continue;
83
+ }
84
+ if (char === "'" || char === '"' || char === "`") {
85
+ quote = char;
86
+ continue;
87
+ }
88
+ if (char === "(") {
89
+ depth += 1;
90
+ continue;
91
+ }
92
+ if (char === ")") {
93
+ depth -= 1;
94
+ if (depth === 0)
95
+ return i;
96
+ }
97
+ }
98
+ return -1;
99
+ }
100
+ function extractExportedComponentSource(filePath, symbolName) {
101
+ if (!filePath || !symbolName)
102
+ return undefined;
103
+ let source = "";
104
+ try {
105
+ source = readFileSync(filePath, "utf8");
106
+ }
107
+ catch {
108
+ return undefined;
109
+ }
110
+ 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");
111
+ const match = pattern.exec(source);
112
+ if (!match || match.index == null)
113
+ return undefined;
114
+ const start = source.lastIndexOf("export", match.index + match[0].length);
115
+ const openParenIndex = source.indexOf("(", match.index + match[0].length - 1);
116
+ if (start < 0 || openParenIndex < 0)
117
+ return undefined;
118
+ const closeParenIndex = findMatchingParen(source, openParenIndex);
119
+ if (closeParenIndex < 0)
120
+ return undefined;
121
+ let end = closeParenIndex + 1;
122
+ while (end < source.length && /\s/.test(source[end]))
123
+ end += 1;
124
+ if (source[end] === ";")
125
+ end += 1;
126
+ return source.slice(start, end).trim();
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // to_manifest
130
+ // ---------------------------------------------------------------------------
131
+ export function toManifest(component, opts = {}) {
132
+ const componentSource = extractExportedComponentSource(opts.filePath, opts.symbolName);
133
+ const base = {
134
+ name: component.name,
135
+ description: component.description,
136
+ component_type: component.componentType,
137
+ language: "typescript",
138
+ is_runnable: component.isRunnable,
139
+ is_eval: component.isEval,
140
+ version_hash: component.versionHash(),
141
+ file_path: opts.filePath,
142
+ symbol_name: opts.symbolName,
143
+ };
144
+ switch (component.componentType) {
145
+ case "tool": {
146
+ const tool = component;
147
+ return {
148
+ ...base,
149
+ fn_present: true,
150
+ is_async: tool.isAsync,
151
+ output_schema: tool.outputSchema,
152
+ source_code: componentSource || tool.fn.toString(),
153
+ source_language: "typescript",
154
+ source_format: componentSource ? "component" : "function",
155
+ };
156
+ }
157
+ case "code_eval": {
158
+ const codeEval = component;
159
+ return {
160
+ ...base,
161
+ fn_present: true,
162
+ is_async: codeEval.isAsync,
163
+ source_code: componentSource || codeEval.fn.toString(),
164
+ source_language: "typescript",
165
+ source_format: componentSource ? "component" : "function",
166
+ model_response_format: codeEval.modelResponseFormat,
167
+ };
168
+ }
169
+ case "skill": {
170
+ const skill = component;
171
+ return {
172
+ ...base,
173
+ instructions: skill.instructions,
174
+ fn_present: skill.fn != null,
175
+ is_async: skill.isAsync,
176
+ tools: skill.tools.map((t) => t.name),
177
+ output_schema: skill.outputSchema,
178
+ source_code: componentSource || skill.fn?.toString(),
179
+ source_language: "typescript",
180
+ source_format: componentSource ? "component" : skill.fn ? "function" : undefined,
181
+ };
182
+ }
183
+ case "agent": {
184
+ const agent = component;
185
+ return {
186
+ ...base,
187
+ instructions: agent.instructions,
188
+ model: agent.model,
189
+ tools: agent.tools.map((t) => t.name),
190
+ skills: agent.skills.map((s) => s.name),
191
+ agents: agent.agents.map((a) => a.name),
192
+ output_schema: agent.outputSchema,
193
+ };
194
+ }
195
+ case "llm_processor": {
196
+ const llmProcessor = component;
197
+ return {
198
+ ...base,
199
+ instructions: llmProcessor.instructions,
200
+ model: llmProcessor.model,
201
+ output_schema: llmProcessor.outputSchema,
202
+ };
203
+ }
204
+ case "llm_eval": {
205
+ const llmEval = component;
206
+ return {
207
+ ...base,
208
+ instructions: llmEval.instructions,
209
+ model: llmEval.model,
210
+ output_schema: llmEval.outputSchema,
211
+ };
212
+ }
213
+ case "orchestration": {
214
+ const orchestration = component;
215
+ const steps = orchestration.steps.map((s) => {
216
+ if (isConditionalLike(s.component)) {
217
+ return {
218
+ name: s.name,
219
+ component: "conditional",
220
+ next: s.next,
221
+ is_conditional: true,
222
+ if_true: s.component.ifTrue,
223
+ if_false: s.component.ifFalse,
224
+ };
225
+ }
226
+ return {
227
+ name: s.name,
228
+ component: s.component.name,
229
+ next: s.next,
230
+ };
231
+ });
232
+ return { ...base, steps };
233
+ }
234
+ case "checklist": {
235
+ const checklist = component;
236
+ return {
237
+ ...base,
238
+ evals: checklist.evals.map((e) => e.name),
239
+ weights: checklist.weights,
240
+ };
241
+ }
242
+ case "eval_tree": {
243
+ const evalTree = component;
244
+ return {
245
+ ...base,
246
+ root: serializeBranch(evalTree.root),
247
+ };
248
+ }
249
+ default:
250
+ return base;
251
+ }
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Collect exported components from a module
255
+ // ---------------------------------------------------------------------------
256
+ export function collectExportedComponents(moduleExports) {
257
+ const components = [];
258
+ const seen = new Set();
259
+ const namespaces = [moduleExports];
260
+ const defaultNamespace = moduleExports.default;
261
+ if (defaultNamespace != null &&
262
+ typeof defaultNamespace === "object" &&
263
+ !Array.isArray(defaultNamespace)) {
264
+ namespaces.push(defaultNamespace);
265
+ }
266
+ for (const namespace of namespaces) {
267
+ for (const value of Object.values(namespace)) {
268
+ if (isComponentLike(value) && !seen.has(value)) {
269
+ seen.add(value);
270
+ components.push(value);
271
+ }
272
+ }
273
+ }
274
+ return components;
275
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * In-memory LLM call trace store backed by AsyncLocalStorage.
3
+ *
4
+ * The CLI sets a TraceContext before calling a component's runner.
5
+ * The bench.wrap() interceptors read the active context and record each LLM call.
6
+ * After execution the CLI flushes the calls and writes them to Supabase.
7
+ */
8
+ import { AsyncLocalStorage } from "async_hooks";
9
+ export interface LLMCall {
10
+ traceId: string;
11
+ parentId: string | null;
12
+ sequenceOrder: number;
13
+ autoraterRunId: string | null;
14
+ datasetItemId: string | null;
15
+ provider: "anthropic" | "openai" | "gemini";
16
+ model: string;
17
+ inputMessages: unknown[];
18
+ output: unknown | null;
19
+ toolCalls: unknown[] | null;
20
+ rawResponse: unknown | null;
21
+ inputTokens: number | null;
22
+ outputTokens: number | null;
23
+ latencyMs: number;
24
+ stopReason: string | null;
25
+ metadata: Record<string, unknown> | null;
26
+ }
27
+ export declare class TraceContext {
28
+ readonly traceId: string;
29
+ readonly autoraterRunId: string | null;
30
+ readonly datasetItemId: string | null;
31
+ private calls;
32
+ private sequence;
33
+ constructor(traceId: string, autoraterRunId?: string | null, datasetItemId?: string | null);
34
+ nextSequence(): number;
35
+ record(call: LLMCall): void;
36
+ flush(): LLMCall[];
37
+ }
38
+ export declare const traceStorage: AsyncLocalStorage<TraceContext>;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * In-memory LLM call trace store backed by AsyncLocalStorage.
3
+ *
4
+ * The CLI sets a TraceContext before calling a component's runner.
5
+ * The bench.wrap() interceptors read the active context and record each LLM call.
6
+ * After execution the CLI flushes the calls and writes them to Supabase.
7
+ */
8
+ import { AsyncLocalStorage } from "async_hooks";
9
+ export class TraceContext {
10
+ traceId;
11
+ autoraterRunId;
12
+ datasetItemId;
13
+ calls = [];
14
+ sequence = 0;
15
+ constructor(traceId, autoraterRunId = null, datasetItemId = null) {
16
+ this.traceId = traceId;
17
+ this.autoraterRunId = autoraterRunId;
18
+ this.datasetItemId = datasetItemId;
19
+ }
20
+ nextSequence() {
21
+ return this.sequence++;
22
+ }
23
+ record(call) {
24
+ this.calls.push(call);
25
+ }
26
+ flush() {
27
+ return this.calls.splice(0);
28
+ }
29
+ }
30
+ export const traceStorage = new AsyncLocalStorage();
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Core types for the bench TypeScript SDK.
3
+ * Mirrors the Python SDK's class-based component model.
4
+ */
5
+ export type JsonSchemaScalar = {
6
+ type: "string" | "number" | "boolean" | "null";
7
+ };
8
+ export type JsonSchemaObject = {
9
+ type: "object";
10
+ properties: Record<string, JsonSchema>;
11
+ required?: string[];
12
+ additionalProperties?: boolean;
13
+ };
14
+ export type JsonSchemaArray = {
15
+ type: "array";
16
+ items: JsonSchema;
17
+ };
18
+ export type JsonSchema = JsonSchemaScalar | JsonSchemaObject | JsonSchemaArray | Record<string, unknown>;
19
+ export type ComponentType = "tool" | "code_eval" | "skill" | "agent" | "llm_processor" | "llm_eval" | "orchestration" | "checklist" | "eval_tree";
20
+ export declare const RUNNABLE_TYPES: Set<ComponentType>;
21
+ export declare const EVAL_TYPES: Set<ComponentType>;
22
+ export interface ToolCall {
23
+ name: string;
24
+ input: Record<string, unknown>;
25
+ id: string;
26
+ _parseError?: string;
27
+ }
28
+ export interface LLMResponse {
29
+ text: string;
30
+ toolCalls: ToolCall[];
31
+ stopReason: string | null;
32
+ }
33
+ export interface RunnerMessage {
34
+ role: "system" | "user" | "assistant";
35
+ content: string;
36
+ }
37
+ export interface RunnerCallOptions {
38
+ messages: RunnerMessage[];
39
+ model: string;
40
+ tools?: Tool[];
41
+ outputSchema?: JsonSchema;
42
+ jsonRepair?: boolean;
43
+ }
44
+ export interface RunnerOptions {
45
+ client: unknown;
46
+ }
47
+ export declare class Runner {
48
+ readonly client: unknown;
49
+ constructor(opts: RunnerOptions);
50
+ buildMessages(component: Pick<BaseComponent, "description"> & {
51
+ instructions?: string;
52
+ }, row: Record<string, unknown>): RunnerMessage[];
53
+ private providerName;
54
+ private buildJsonRepairMessages;
55
+ private requestJsonRepair;
56
+ maybeRepairJson(args: {
57
+ model: string;
58
+ outputSchema?: JsonSchema;
59
+ jsonRepair?: boolean;
60
+ text: string;
61
+ }): Promise<string>;
62
+ callLlm(opts: RunnerCallOptions): Promise<LLMResponse>;
63
+ }
64
+ export declare abstract class BaseComponent {
65
+ abstract readonly componentType: ComponentType;
66
+ abstract readonly name: string;
67
+ abstract readonly description: string;
68
+ get isRunnable(): boolean;
69
+ get isEval(): boolean;
70
+ abstract versionHash(): string;
71
+ }
72
+ export interface ToolOptions {
73
+ name: string;
74
+ description: string;
75
+ fn: (...args: unknown[]) => unknown;
76
+ inputSchema: JsonSchema;
77
+ outputSchema?: JsonSchema;
78
+ }
79
+ export declare class Tool extends BaseComponent {
80
+ readonly componentType: "tool";
81
+ readonly name: string;
82
+ readonly description: string;
83
+ readonly fn: (...args: unknown[]) => unknown;
84
+ readonly inputSchema: JsonSchema;
85
+ readonly outputSchema?: JsonSchema;
86
+ readonly isAsync: boolean;
87
+ constructor(opts: ToolOptions);
88
+ versionHash(): string;
89
+ }
90
+ export interface CodeEvalOptions {
91
+ name: string;
92
+ description: string;
93
+ fn: (...args: unknown[]) => unknown;
94
+ modelResponseFormat?: "JSON" | "TEXT" | "YAML";
95
+ }
96
+ export declare class CodeEval extends BaseComponent {
97
+ readonly componentType: "code_eval";
98
+ readonly name: string;
99
+ readonly description: string;
100
+ readonly fn: (...args: unknown[]) => unknown;
101
+ readonly isAsync: boolean;
102
+ readonly modelResponseFormat: "JSON" | "TEXT" | "YAML";
103
+ constructor(opts: CodeEvalOptions);
104
+ versionHash(): string;
105
+ }
106
+ export interface SkillOptions {
107
+ name: string;
108
+ description: string;
109
+ instructions: string;
110
+ tools?: Tool[];
111
+ fn?: (...args: unknown[]) => unknown;
112
+ outputSchema?: JsonSchema;
113
+ }
114
+ export declare class Skill extends BaseComponent {
115
+ readonly componentType: "skill";
116
+ readonly name: string;
117
+ readonly description: string;
118
+ readonly instructions: string;
119
+ readonly tools: Tool[];
120
+ readonly fn?: (...args: unknown[]) => unknown;
121
+ readonly outputSchema?: JsonSchema;
122
+ readonly isAsync: boolean;
123
+ constructor(opts: SkillOptions);
124
+ versionHash(): string;
125
+ }
126
+ export interface AgentOptions {
127
+ name: string;
128
+ description: string;
129
+ instructions: string;
130
+ model: string;
131
+ runner?: Runner;
132
+ tools?: Tool[];
133
+ skills?: Skill[];
134
+ agents?: Agent[];
135
+ outputSchema?: JsonSchema;
136
+ jsonRepair?: boolean;
137
+ }
138
+ export declare class Agent extends BaseComponent {
139
+ readonly componentType: "agent";
140
+ readonly name: string;
141
+ readonly description: string;
142
+ readonly instructions: string;
143
+ readonly model: string;
144
+ readonly runner?: Runner;
145
+ readonly tools: Tool[];
146
+ readonly skills: Skill[];
147
+ readonly agents: Agent[];
148
+ readonly outputSchema?: JsonSchema;
149
+ readonly jsonRepair: boolean;
150
+ constructor(opts: AgentOptions);
151
+ versionHash(): string;
152
+ }
153
+ export interface LLMProcessorOptions {
154
+ name: string;
155
+ description: string;
156
+ instructions: string;
157
+ model: string;
158
+ runner?: Runner;
159
+ outputSchema?: JsonSchema;
160
+ jsonRepair?: boolean;
161
+ }
162
+ export declare class LLMProcessor extends BaseComponent {
163
+ readonly componentType: "llm_processor";
164
+ readonly name: string;
165
+ readonly description: string;
166
+ readonly instructions: string;
167
+ readonly model: string;
168
+ readonly runner?: Runner;
169
+ readonly outputSchema?: JsonSchema;
170
+ readonly jsonRepair: boolean;
171
+ constructor(opts: LLMProcessorOptions);
172
+ versionHash(): string;
173
+ }
174
+ export interface LLMEvalOptions {
175
+ name: string;
176
+ description: string;
177
+ instructions: string;
178
+ model: string;
179
+ runner?: Runner;
180
+ outputSchema?: JsonSchema;
181
+ jsonRepair?: boolean;
182
+ }
183
+ export declare class LLMEval extends BaseComponent {
184
+ readonly componentType: "llm_eval";
185
+ readonly name: string;
186
+ readonly description: string;
187
+ readonly instructions: string;
188
+ readonly model: string;
189
+ readonly runner?: Runner;
190
+ readonly outputSchema?: JsonSchema;
191
+ readonly jsonRepair: boolean;
192
+ constructor(opts: LLMEvalOptions);
193
+ versionHash(): string;
194
+ }
195
+ export interface ConditionalOptions {
196
+ condition: (ctx: Record<string, unknown>) => boolean;
197
+ ifTrue: string;
198
+ ifFalse: string;
199
+ }
200
+ export declare class Conditional {
201
+ readonly condition: (ctx: Record<string, unknown>) => boolean;
202
+ readonly ifTrue: string;
203
+ readonly ifFalse: string;
204
+ constructor(opts: ConditionalOptions);
205
+ }
206
+ export interface StepOptions {
207
+ name: string;
208
+ component: BaseComponent | Conditional;
209
+ next?: string[];
210
+ }
211
+ export declare class Step {
212
+ readonly name: string;
213
+ readonly component: BaseComponent | Conditional;
214
+ readonly next: string[];
215
+ constructor(opts: StepOptions);
216
+ }
217
+ export interface OrchestrationOptions {
218
+ name: string;
219
+ description: string;
220
+ steps: Step[];
221
+ }
222
+ export declare class Orchestration extends BaseComponent {
223
+ readonly componentType: "orchestration";
224
+ readonly name: string;
225
+ readonly description: string;
226
+ readonly steps: Step[];
227
+ constructor(opts: OrchestrationOptions);
228
+ versionHash(): string;
229
+ }
230
+ export declare class Leaf {
231
+ readonly eval: BaseComponent;
232
+ constructor(opts: {
233
+ eval: BaseComponent;
234
+ });
235
+ }
236
+ export declare class Edge {
237
+ readonly weight: number;
238
+ readonly node: Leaf | Branch;
239
+ constructor(opts: {
240
+ weight: number;
241
+ node: Leaf | Branch;
242
+ });
243
+ }
244
+ export declare class Branch {
245
+ readonly eval: BaseComponent;
246
+ readonly threshold: number;
247
+ readonly ifPass: Edge[];
248
+ readonly ifFail: Edge[];
249
+ constructor(opts: {
250
+ eval: BaseComponent;
251
+ threshold: number;
252
+ ifPass: Edge[];
253
+ ifFail: Edge[];
254
+ });
255
+ }
256
+ export interface ChecklistOptions {
257
+ name: string;
258
+ description: string;
259
+ evals: BaseComponent[];
260
+ weights?: number[];
261
+ }
262
+ export declare class Checklist extends BaseComponent {
263
+ readonly componentType: "checklist";
264
+ readonly name: string;
265
+ readonly description: string;
266
+ readonly evals: BaseComponent[];
267
+ readonly weights?: number[];
268
+ constructor(opts: ChecklistOptions);
269
+ versionHash(): string;
270
+ }
271
+ export interface EvalTreeOptions {
272
+ name: string;
273
+ description: string;
274
+ root: Branch;
275
+ }
276
+ export declare class EvalTree extends BaseComponent {
277
+ readonly componentType: "eval_tree";
278
+ readonly name: string;
279
+ readonly description: string;
280
+ readonly root: Branch;
281
+ constructor(opts: EvalTreeOptions);
282
+ versionHash(): string;
283
+ }