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