agency-lang 0.0.90 → 0.0.92
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/lib/backends/agencyGenerator.js +7 -4
- package/dist/lib/backends/typescriptBuilder.js +8 -7
- package/dist/lib/debugger/driver.js +10 -3
- package/dist/lib/debugger/ui.js +1 -0
- package/dist/lib/ir/builders.d.ts +6 -2
- package/dist/lib/ir/builders.js +4 -4
- package/dist/lib/ir/fluent.d.ts +3 -1
- package/dist/lib/ir/fluent.js +2 -2
- package/dist/lib/ir/prettyPrint.js +4 -2
- package/dist/lib/ir/tsIR.d.ts +1 -0
- package/dist/lib/parsers/access.test.js +67 -0
- package/dist/lib/parsers/binop.test.js +62 -0
- package/dist/lib/parsers/parsers.js +21 -12
- package/dist/lib/runtime/hooks.d.ts +1 -0
- package/dist/lib/runtime/interrupts.js +4 -2
- package/dist/lib/runtime/prompt.d.ts +2 -0
- package/dist/lib/runtime/prompt.js +17 -8
- package/dist/lib/runtime/runner.d.ts +3 -0
- package/dist/lib/runtime/runner.js +10 -0
- package/dist/lib/runtime/state/checkpointStore.d.ts +1 -0
- package/dist/lib/runtime/state/checkpointStore.js +18 -7
- package/dist/lib/runtime/state/checkpointStore.test.js +1 -1
- package/dist/lib/runtime/state/context.d.ts +4 -0
- package/dist/lib/runtime/state/context.js +23 -0
- package/dist/lib/types/access.d.ts +3 -0
- package/dist/lib/types/binop.d.ts +1 -1
- package/dist/lib/types/binop.js +1 -0
- package/package.json +2 -2
- package/stdlib/agent.js +719 -0
- package/stdlib/array.js +1910 -0
- package/stdlib/consensus.js +359 -0
- package/stdlib/firstValid.js +384 -0
- package/stdlib/fs.js +1400 -0
- package/stdlib/index.js +1989 -0
- package/stdlib/math.js +704 -0
- package/stdlib/object.js +945 -1143
- package/stdlib/path.js +1107 -0
- package/stdlib/retry.js +518 -0
- package/stdlib/sample.js +350 -0
- package/stdlib/shell.js +1307 -0
- package/stdlib/strategy.js +1092 -0
- package/stdlib/system.js +850 -0
- package/stdlib/weather.js +697 -0
|
@@ -793,13 +793,16 @@ export class AgencyGenerator {
|
|
|
793
793
|
return assigned ? multiLine : this.indentStr(multiLine);
|
|
794
794
|
}
|
|
795
795
|
processAccessChainElement(node) {
|
|
796
|
+
const dot = node.optional ? "?." : ".";
|
|
796
797
|
switch (node.kind) {
|
|
797
798
|
case "property":
|
|
798
|
-
return
|
|
799
|
-
case "index":
|
|
800
|
-
|
|
799
|
+
return `${dot}${node.name}`;
|
|
800
|
+
case "index": {
|
|
801
|
+
const inner = this.processNode(node.index).trim();
|
|
802
|
+
return node.optional ? `?.[${inner}]` : `[${inner}]`;
|
|
803
|
+
}
|
|
801
804
|
case "methodCall":
|
|
802
|
-
return
|
|
805
|
+
return `${dot}${this.generateFunctionCallExpression(node.functionCall, "valueAccess")}`;
|
|
803
806
|
default:
|
|
804
807
|
throw new Error(`Unknown access chain element kind: ${node.kind}`);
|
|
805
808
|
}
|
|
@@ -661,10 +661,10 @@ export class TypeScriptBuilder {
|
|
|
661
661
|
for (const element of node.chain) {
|
|
662
662
|
switch (element.kind) {
|
|
663
663
|
case "property":
|
|
664
|
-
result = ts.prop(result, element.name);
|
|
664
|
+
result = ts.prop(result, element.name, { optional: element.optional });
|
|
665
665
|
break;
|
|
666
666
|
case "index":
|
|
667
|
-
result = ts.index(result, this.processNode(element.index));
|
|
667
|
+
result = ts.index(result, this.processNode(element.index), { optional: element.optional });
|
|
668
668
|
break;
|
|
669
669
|
case "methodCall": {
|
|
670
670
|
const callNode = this.generateFunctionCallExpression(element.functionCall, "valueAccess");
|
|
@@ -675,15 +675,14 @@ export class TypeScriptBuilder {
|
|
|
675
675
|
const args = isClassMethod
|
|
676
676
|
? [...callNode.arguments, this.buildMethodCallConfig()]
|
|
677
677
|
: callNode.arguments;
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
.call(args)
|
|
681
|
-
.done();
|
|
678
|
+
const propNode = ts.prop(result, callNode.callee.name, { optional: element.optional });
|
|
679
|
+
const callExpr = ts.call(propNode, args);
|
|
682
680
|
result = isClassMethod ? ts.await(callExpr) : callExpr;
|
|
683
681
|
}
|
|
684
682
|
else {
|
|
685
683
|
// Fallback for complex cases (e.g. await-wrapped)
|
|
686
|
-
|
|
684
|
+
const dot = element.optional ? "?." : ".";
|
|
685
|
+
result = ts.raw(`${this.str(result)}${dot}${this.str(callNode)}`);
|
|
687
686
|
}
|
|
688
687
|
break;
|
|
689
688
|
}
|
|
@@ -1140,6 +1139,7 @@ export class TypeScriptBuilder {
|
|
|
1140
1139
|
functionName: ts.str(functionName),
|
|
1141
1140
|
args: ts.obj(argsObj),
|
|
1142
1141
|
isBuiltin: ts.bool(false),
|
|
1142
|
+
moduleId: ts.str(this.moduleId),
|
|
1143
1143
|
}),
|
|
1144
1144
|
];
|
|
1145
1145
|
// Param assignments to stack
|
|
@@ -1778,6 +1778,7 @@ export class TypeScriptBuilder {
|
|
|
1778
1778
|
runPromptEntries.maxToolCallRounds = ts.num(this.agencyConfig.maxToolCallRounds || 10);
|
|
1779
1779
|
runPromptEntries.interruptData = ts.raw("__state?.interruptData");
|
|
1780
1780
|
runPromptEntries.removedTools = ts.self("__removedTools");
|
|
1781
|
+
runPromptEntries.checkpointInfo = ts.raw("runner.getCheckpointInfo()");
|
|
1781
1782
|
const runPromptCall = $(ts.id("runPrompt"))
|
|
1782
1783
|
.call([ts.obj(runPromptEntries)])
|
|
1783
1784
|
.done();
|
|
@@ -59,20 +59,22 @@ export class DebuggerDriver {
|
|
|
59
59
|
getCallbacks() {
|
|
60
60
|
return {
|
|
61
61
|
onFunctionStart: (data) => {
|
|
62
|
-
const
|
|
62
|
+
const isAgency = data.moduleId?.endsWith(".agency");
|
|
63
63
|
this.debuggerState.enterCall();
|
|
64
64
|
this.ui.state.pushCallStackEntry({
|
|
65
65
|
functionName: data.functionName,
|
|
66
|
-
moduleId:
|
|
66
|
+
moduleId: isAgency ? data.moduleId : "(ts)",
|
|
67
67
|
line: 0,
|
|
68
68
|
});
|
|
69
|
-
if (!
|
|
69
|
+
if (!isAgency) {
|
|
70
70
|
this.ui.state.log(`[ts] ${data.functionName}()`);
|
|
71
71
|
}
|
|
72
|
+
this.ui.render();
|
|
72
73
|
},
|
|
73
74
|
onFunctionEnd: (data) => {
|
|
74
75
|
this.debuggerState.exitCall();
|
|
75
76
|
this.ui.state.removeWithFuncName(data.functionName);
|
|
77
|
+
this.ui.render();
|
|
76
78
|
},
|
|
77
79
|
onNodeStart: (data) => {
|
|
78
80
|
// gets triggered on every statement, as debugger resumes node execution.
|
|
@@ -80,10 +82,12 @@ export class DebuggerDriver {
|
|
|
80
82
|
},
|
|
81
83
|
onNodeEnd: (data) => {
|
|
82
84
|
this.ui.state.log(`<- node: ${data.nodeName}`);
|
|
85
|
+
this.ui.render();
|
|
83
86
|
},
|
|
84
87
|
onLLMCallStart: (data) => {
|
|
85
88
|
const model = typeof data.model === "string" ? data.model : "unknown";
|
|
86
89
|
this.ui.state.log(`Calling LLM: ${model}...`);
|
|
90
|
+
this.ui.render();
|
|
87
91
|
},
|
|
88
92
|
onLLMCallEnd: (data) => {
|
|
89
93
|
const tokens = data.usage
|
|
@@ -91,12 +95,15 @@ export class DebuggerDriver {
|
|
|
91
95
|
: "unknown tokens";
|
|
92
96
|
const time = `${round(data.timeTaken)}ms`;
|
|
93
97
|
this.ui.state.log(`LLM returned (${tokens}, ${time})`);
|
|
98
|
+
this.ui.render();
|
|
94
99
|
},
|
|
95
100
|
onToolCallStart: (data) => {
|
|
96
101
|
this.ui.state.log(`Tool call: ${data.toolName}()`);
|
|
102
|
+
this.ui.render();
|
|
97
103
|
},
|
|
98
104
|
onToolCallEnd: (data) => {
|
|
99
105
|
this.ui.state.log(`Tool done: ${data.toolName} (${round(data.timeTaken)}ms)`);
|
|
106
|
+
this.ui.render();
|
|
100
107
|
},
|
|
101
108
|
};
|
|
102
109
|
}
|
package/dist/lib/debugger/ui.js
CHANGED
|
@@ -381,6 +381,7 @@ export class DebuggerUI {
|
|
|
381
381
|
// Format messages
|
|
382
382
|
const content = threadData.messages
|
|
383
383
|
.map((m) => {
|
|
384
|
+
// if its not a string,call json.strinfigy
|
|
384
385
|
const truncated = m.content.length > 200 ? m.content.slice(0, 197) + "..." : m.content;
|
|
385
386
|
return ` ${this.bold(`[${this.fmt(m.role)}]`)} ${this.fmt(truncated)}`;
|
|
386
387
|
})
|
|
@@ -51,8 +51,12 @@ export declare const ts: {
|
|
|
51
51
|
parenLeft?: boolean;
|
|
52
52
|
parenRight?: boolean;
|
|
53
53
|
}): TsBinOp;
|
|
54
|
-
prop(object: TsNode, property: string
|
|
55
|
-
|
|
54
|
+
prop(object: TsNode, property: string, opts?: {
|
|
55
|
+
optional?: boolean;
|
|
56
|
+
}): TsPropertyAccess;
|
|
57
|
+
index(object: TsNode, property: TsNode, opts?: {
|
|
58
|
+
optional?: boolean;
|
|
59
|
+
}): TsPropertyAccess;
|
|
56
60
|
spread(expr: TsNode): TsSpread;
|
|
57
61
|
id(name: string): TsIdentifier;
|
|
58
62
|
str(value: string): TsStringLiteral;
|
package/dist/lib/ir/builders.js
CHANGED
|
@@ -166,11 +166,11 @@ export const ts = {
|
|
|
166
166
|
parenRight: opts?.parenRight,
|
|
167
167
|
};
|
|
168
168
|
},
|
|
169
|
-
prop(object, property) {
|
|
170
|
-
return { kind: "propertyAccess", object, property, computed: false };
|
|
169
|
+
prop(object, property, opts) {
|
|
170
|
+
return { kind: "propertyAccess", object, property, computed: false, ...(opts?.optional && { optional: true }) };
|
|
171
171
|
},
|
|
172
|
-
index(object, property) {
|
|
173
|
-
return { kind: "propertyAccess", object, property, computed: true };
|
|
172
|
+
index(object, property, opts) {
|
|
173
|
+
return { kind: "propertyAccess", object, property, computed: true, ...(opts?.optional && { optional: true }) };
|
|
174
174
|
},
|
|
175
175
|
spread(expr) {
|
|
176
176
|
return { kind: "spread", expr };
|
package/dist/lib/ir/fluent.d.ts
CHANGED
|
@@ -9,7 +9,9 @@ export declare class TsChain {
|
|
|
9
9
|
readonly node: TsNode;
|
|
10
10
|
constructor(node: TsNode);
|
|
11
11
|
/** Property access: .prop("foo") → ts.prop(this, "foo") */
|
|
12
|
-
prop(name: string
|
|
12
|
+
prop(name: string, opts?: {
|
|
13
|
+
optional?: boolean;
|
|
14
|
+
}): TsChain;
|
|
13
15
|
map(fn: TsNode): TsChain;
|
|
14
16
|
/** Computed index: .index(expr) → ts.index(this, expr) */
|
|
15
17
|
index(expr: TsNode): TsChain;
|
package/dist/lib/ir/fluent.js
CHANGED
|
@@ -11,8 +11,8 @@ export class TsChain {
|
|
|
11
11
|
this.node = node;
|
|
12
12
|
}
|
|
13
13
|
/** Property access: .prop("foo") → ts.prop(this, "foo") */
|
|
14
|
-
prop(name) {
|
|
15
|
-
return new TsChain(ts.prop(this.node, name));
|
|
14
|
+
prop(name, opts) {
|
|
15
|
+
return new TsChain(ts.prop(this.node, name, opts));
|
|
16
16
|
}
|
|
17
17
|
map(fn) {
|
|
18
18
|
return new TsChain(ts.call(ts.prop(this.node, "map"), [fn]));
|
|
@@ -189,9 +189,11 @@ export function printTs(node, indent = 0) {
|
|
|
189
189
|
case "propertyAccess": {
|
|
190
190
|
const obj = printTs(node.object, indent);
|
|
191
191
|
if (node.computed) {
|
|
192
|
-
|
|
192
|
+
const prefix = node.optional ? "?." : "";
|
|
193
|
+
return `${obj}${prefix}[${printTs(node.property, indent)}]`;
|
|
193
194
|
}
|
|
194
|
-
|
|
195
|
+
const dot = node.optional ? "?." : ".";
|
|
196
|
+
return `${obj}${dot}${node.property}`;
|
|
195
197
|
}
|
|
196
198
|
case "spread":
|
|
197
199
|
return `...${printTs(node.expr, indent)}`;
|
package/dist/lib/ir/tsIR.d.ts
CHANGED
|
@@ -340,6 +340,73 @@ describe("valueAccessParser", () => {
|
|
|
340
340
|
}
|
|
341
341
|
});
|
|
342
342
|
});
|
|
343
|
+
describe("optional chaining", () => {
|
|
344
|
+
it('should parse "foo?.bar" with optional property access', () => {
|
|
345
|
+
const result = valueAccessParser("foo?.bar");
|
|
346
|
+
expect(result.success).toBe(true);
|
|
347
|
+
if (result.success) {
|
|
348
|
+
expect(result.result).toEqualWithoutLoc({
|
|
349
|
+
type: "valueAccess",
|
|
350
|
+
base: { type: "variableName", value: "foo" },
|
|
351
|
+
chain: [{ kind: "property", name: "bar", optional: true }],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
it('should parse "foo?.bar?.baz" with chained optional access', () => {
|
|
356
|
+
const result = valueAccessParser("foo?.bar?.baz");
|
|
357
|
+
expect(result.success).toBe(true);
|
|
358
|
+
if (result.success) {
|
|
359
|
+
expect(result.result).toEqualWithoutLoc({
|
|
360
|
+
type: "valueAccess",
|
|
361
|
+
base: { type: "variableName", value: "foo" },
|
|
362
|
+
chain: [
|
|
363
|
+
{ kind: "property", name: "bar", optional: true },
|
|
364
|
+
{ kind: "property", name: "baz", optional: true },
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
it('should parse "foo?.bar.baz" mixing optional and regular access', () => {
|
|
370
|
+
const result = valueAccessParser("foo?.bar.baz");
|
|
371
|
+
expect(result.success).toBe(true);
|
|
372
|
+
if (result.success) {
|
|
373
|
+
expect(result.result).toEqualWithoutLoc({
|
|
374
|
+
type: "valueAccess",
|
|
375
|
+
base: { type: "variableName", value: "foo" },
|
|
376
|
+
chain: [
|
|
377
|
+
{ kind: "property", name: "bar", optional: true },
|
|
378
|
+
{ kind: "property", name: "baz" },
|
|
379
|
+
],
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
it('should parse "arr?.[0]" with optional index access', () => {
|
|
384
|
+
const result = valueAccessParser("arr?.[0]");
|
|
385
|
+
expect(result.success).toBe(true);
|
|
386
|
+
if (result.success) {
|
|
387
|
+
expect(result.result).toEqualWithoutLoc({
|
|
388
|
+
type: "valueAccess",
|
|
389
|
+
base: { type: "variableName", value: "arr" },
|
|
390
|
+
chain: [{ kind: "index", index: { type: "number", value: "0" }, optional: true }],
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
it('should parse "obj?.method()" with optional method call', () => {
|
|
395
|
+
const result = valueAccessParser("obj?.method()");
|
|
396
|
+
expect(result.success).toBe(true);
|
|
397
|
+
if (result.success) {
|
|
398
|
+
expect(result.result).toEqualWithoutLoc({
|
|
399
|
+
type: "valueAccess",
|
|
400
|
+
base: { type: "variableName", value: "obj" },
|
|
401
|
+
chain: [{
|
|
402
|
+
kind: "methodCall",
|
|
403
|
+
functionCall: { type: "functionCall", functionName: "method", arguments: [] },
|
|
404
|
+
optional: true,
|
|
405
|
+
}],
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
343
410
|
describe("failure cases", () => {
|
|
344
411
|
it('should fail to parse ""', () => {
|
|
345
412
|
expect(valueAccessParser("").success).toBe(false);
|
|
@@ -524,6 +524,68 @@ describe("binOpParser", () => {
|
|
|
524
524
|
}
|
|
525
525
|
});
|
|
526
526
|
});
|
|
527
|
+
// Nullish coalescing operator
|
|
528
|
+
describe("nullish coalescing operator", () => {
|
|
529
|
+
it('should parse "a ?? b"', () => {
|
|
530
|
+
const result = binOpParser("a ?? b");
|
|
531
|
+
expect(result.success).toBe(true);
|
|
532
|
+
if (result.success) {
|
|
533
|
+
expect(result.result).toEqualWithoutLoc({
|
|
534
|
+
type: "binOpExpression",
|
|
535
|
+
operator: "??",
|
|
536
|
+
left: { type: "variableName", value: "a" },
|
|
537
|
+
right: { type: "variableName", value: "b" },
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
it('should parse "a ?? b ?? c" as left-associative', () => {
|
|
542
|
+
const result = binOpParser("a ?? b ?? c");
|
|
543
|
+
expect(result.success).toBe(true);
|
|
544
|
+
if (result.success) {
|
|
545
|
+
expect(result.result).toEqualWithoutLoc({
|
|
546
|
+
type: "binOpExpression",
|
|
547
|
+
operator: "??",
|
|
548
|
+
left: {
|
|
549
|
+
type: "binOpExpression",
|
|
550
|
+
operator: "??",
|
|
551
|
+
left: { type: "variableName", value: "a" },
|
|
552
|
+
right: { type: "variableName", value: "b" },
|
|
553
|
+
},
|
|
554
|
+
right: { type: "variableName", value: "c" },
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
it('should parse "a ?? 42"', () => {
|
|
559
|
+
const result = binOpParser("a ?? 42");
|
|
560
|
+
expect(result.success).toBe(true);
|
|
561
|
+
if (result.success) {
|
|
562
|
+
expect(result.result).toEqualWithoutLoc({
|
|
563
|
+
type: "binOpExpression",
|
|
564
|
+
operator: "??",
|
|
565
|
+
left: { type: "variableName", value: "a" },
|
|
566
|
+
right: { type: "number", value: "42" },
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
it('should parse ?? with lower precedence than comparison', () => {
|
|
571
|
+
const result = binOpParser("a == b ?? c");
|
|
572
|
+
expect(result.success).toBe(true);
|
|
573
|
+
if (result.success) {
|
|
574
|
+
// Should parse as (a == b) ?? c
|
|
575
|
+
expect(result.result).toEqualWithoutLoc({
|
|
576
|
+
type: "binOpExpression",
|
|
577
|
+
operator: "??",
|
|
578
|
+
left: {
|
|
579
|
+
type: "binOpExpression",
|
|
580
|
+
operator: "==",
|
|
581
|
+
left: { type: "variableName", value: "a" },
|
|
582
|
+
right: { type: "variableName", value: "b" },
|
|
583
|
+
},
|
|
584
|
+
right: { type: "variableName", value: "c" },
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
});
|
|
527
589
|
// Failure cases
|
|
528
590
|
describe("regex match operators", () => {
|
|
529
591
|
const testCases = [
|
|
@@ -22,7 +22,7 @@ export const varNameChar = oneOf("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST
|
|
|
22
22
|
However, because every agency code gets rendered in a template that imports some standard functions,
|
|
23
23
|
the line numbers would be off if we didn't account for the template lines.
|
|
24
24
|
*/
|
|
25
|
-
const AGENCY_TEMPLATE_OFFSET =
|
|
25
|
+
const AGENCY_TEMPLATE_OFFSET = 3;
|
|
26
26
|
/**
|
|
27
27
|
* Wraps a parser to add a `loc` field from tarsec's withSpan.
|
|
28
28
|
* Converts Span { start: Position, end: Position } to SourceLocation { line, col, start, end }.
|
|
@@ -409,27 +409,35 @@ export const functionCallParser = label("a function call", _functionCallParser);
|
|
|
409
409
|
// =============================================================================
|
|
410
410
|
// access.ts
|
|
411
411
|
// =============================================================================
|
|
412
|
-
// Parse
|
|
412
|
+
// Parse "?." or "." — returns true for optional, false for regular
|
|
413
|
+
const dotParser = or(map(str("?."), () => true), map(char("."), () => false));
|
|
414
|
+
// Parse a single chain element: .method(), ?.method(), .property, ?.property, [index], ?.[index]
|
|
413
415
|
const dotMethodCallParser = (input) => {
|
|
414
|
-
|
|
415
|
-
const dotResult = char(".")(input);
|
|
416
|
+
const dotResult = dotParser(input);
|
|
416
417
|
if (!dotResult.success)
|
|
417
418
|
return failure("expected dot", input);
|
|
418
|
-
const
|
|
419
|
+
const optional = dotResult.result;
|
|
420
|
+
const afterDot = dotResult.rest;
|
|
421
|
+
// First try: functionCall (name + parens)
|
|
422
|
+
const fcResult = _functionCallParser(afterDot);
|
|
419
423
|
if (fcResult.success) {
|
|
420
|
-
return success({ kind: "methodCall", functionCall: fcResult.result }, fcResult.rest);
|
|
424
|
+
return success({ kind: "methodCall", functionCall: fcResult.result, ...(optional && { optional: true }) }, fcResult.rest);
|
|
421
425
|
}
|
|
422
|
-
// Second try:
|
|
423
|
-
const nameResult = variableNameParser(
|
|
426
|
+
// Second try: just a property name
|
|
427
|
+
const nameResult = variableNameParser(afterDot);
|
|
424
428
|
if (nameResult.success) {
|
|
425
|
-
return success({ kind: "property", name: nameResult.result.value }, nameResult.rest);
|
|
429
|
+
return success({ kind: "property", name: nameResult.result.value, ...(optional && { optional: true }) }, nameResult.rest);
|
|
426
430
|
}
|
|
427
431
|
return failure("expected property name or method call after dot", input);
|
|
428
432
|
};
|
|
433
|
+
// Parse "?.[" or "[" — returns true for optional, false for regular
|
|
434
|
+
const bracketParser = or(map(str("?.["), () => true), map(char("["), () => false));
|
|
429
435
|
const indexChainParser = (input) => {
|
|
430
|
-
const parser = seqC(
|
|
436
|
+
const parser = seqC(capture(bracketParser, "optional"), optionalSpaces, capture(lazy(() => exprParser), "index"), optionalSpaces, char("]"));
|
|
431
437
|
const result = parser(input);
|
|
432
|
-
|
|
438
|
+
if (!result.success)
|
|
439
|
+
return result;
|
|
440
|
+
return success({ kind: "index", index: result.result.index, ...(result.result.optional && { optional: true }) }, result.rest);
|
|
433
441
|
};
|
|
434
442
|
const chainElementParser = or(dotMethodCallParser, indexChainParser);
|
|
435
443
|
export const _valueAccessParser = (input) => {
|
|
@@ -607,8 +615,9 @@ export const exprParser = label("an expression", buildExpressionParser(atom, [
|
|
|
607
615
|
[
|
|
608
616
|
{ op: wsOp("&&"), assoc: "left", apply: makeBinOp("&&") },
|
|
609
617
|
],
|
|
610
|
-
// Precedence 1: logical OR
|
|
618
|
+
// Precedence 1: logical OR, nullish coalescing
|
|
611
619
|
[
|
|
620
|
+
{ op: wsOp("??"), assoc: "left", apply: makeBinOp("??") },
|
|
612
621
|
{ op: wsOp("||"), assoc: "left", apply: makeBinOp("||") },
|
|
613
622
|
],
|
|
614
623
|
// Precedence 0: catch (unwrap Result with fallback)
|
|
@@ -114,8 +114,10 @@ export async function respondToInterrupt(args) {
|
|
|
114
114
|
execCtx.debuggerState = metadata.debugger;
|
|
115
115
|
}
|
|
116
116
|
let interruptData = interrupt.interruptData || {};
|
|
117
|
-
if (interrupt.debugger) {
|
|
118
|
-
// Debugger-generated interrupts don't carry tool-call data
|
|
117
|
+
if (interrupt.debugger && !interrupt.interruptData?.toolCall) {
|
|
118
|
+
// Debugger-generated interrupts don't carry tool-call data,
|
|
119
|
+
// unless the debug pause happened inside a tool call during an LLM call —
|
|
120
|
+
// in that case, keep interruptData so runPrompt can resume mid-conversation.
|
|
119
121
|
interruptData = undefined;
|
|
120
122
|
}
|
|
121
123
|
else {
|
|
@@ -2,6 +2,7 @@ import * as smoltalk from "smoltalk";
|
|
|
2
2
|
import { MessageThread } from "./state/messageThread.js";
|
|
3
3
|
import { InterruptData } from "./interrupts.js";
|
|
4
4
|
import type { RuntimeContext } from "./state/context.js";
|
|
5
|
+
import type { SourceLocationOpts } from "./state/checkpointStore.js";
|
|
5
6
|
import { GraphState } from "./types.js";
|
|
6
7
|
export interface ToolHandler {
|
|
7
8
|
name: string;
|
|
@@ -20,4 +21,5 @@ export declare function runPrompt(args: {
|
|
|
20
21
|
maxToolCallRounds?: number;
|
|
21
22
|
interruptData?: InterruptData;
|
|
22
23
|
removedTools?: string[];
|
|
24
|
+
checkpointInfo?: SourceLocationOpts;
|
|
23
25
|
}): Promise<any>;
|
|
@@ -152,6 +152,7 @@ async function executeToolCalls({ toolCalls, toolHandlers, messages, ctx, client
|
|
|
152
152
|
isToolCall: true,
|
|
153
153
|
});
|
|
154
154
|
const toolCallStartTime = performance.now();
|
|
155
|
+
ctx.enterToolCall();
|
|
155
156
|
try {
|
|
156
157
|
result = await handler.execute(...params);
|
|
157
158
|
}
|
|
@@ -182,6 +183,9 @@ async function executeToolCalls({ toolCalls, toolHandlers, messages, ctx, client
|
|
|
182
183
|
}
|
|
183
184
|
continue;
|
|
184
185
|
}
|
|
186
|
+
finally {
|
|
187
|
+
ctx.exitToolCall();
|
|
188
|
+
}
|
|
185
189
|
// Tool returned a failure Result — handle retry logic
|
|
186
190
|
if (isFailure(result)) {
|
|
187
191
|
const errorMessage = typeof result.error === "string" ? result.error : String(result.error);
|
|
@@ -250,7 +254,7 @@ async function executeToolCalls({ toolCalls, toolHandlers, messages, ctx, client
|
|
|
250
254
|
return { isInterrupt: false, messages };
|
|
251
255
|
}
|
|
252
256
|
export async function runPrompt(args) {
|
|
253
|
-
const { ctx, prompt, responseFormat, maxToolCallRounds = 10, removedTools = [], } = args;
|
|
257
|
+
const { ctx, prompt, responseFormat, maxToolCallRounds = 10, removedTools = [], checkpointInfo, } = args;
|
|
254
258
|
// Extract tool registry entries from clientConfig.tools and split into
|
|
255
259
|
// definitions (for smoltalk) and handlers (for execution).
|
|
256
260
|
const toolEntries = (args.clientConfig?.tools || []).map((entry) => entry);
|
|
@@ -330,13 +334,18 @@ export async function runPrompt(args) {
|
|
|
330
334
|
messages: messages.getMessages(),
|
|
331
335
|
model: clientConfig.model,
|
|
332
336
|
});
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
if (interrupt.debugger === false) {
|
|
338
|
+
// For real user interrupts, create a checkpoint at the LLM call site
|
|
339
|
+
// so we can resume the conversation. Debug interrupts keep their
|
|
340
|
+
// original checkpoint from debugStep (pointing to the tool's source).
|
|
341
|
+
const checkpointId = ctx.checkpoints.create(ctx, {
|
|
342
|
+
moduleId: checkpointInfo?.moduleId ?? "",
|
|
343
|
+
scopeName: checkpointInfo?.scopeName ?? "",
|
|
344
|
+
stepPath: checkpointInfo?.stepPath ?? "",
|
|
345
|
+
});
|
|
346
|
+
interrupt.checkpointId = checkpointId;
|
|
347
|
+
interrupt.checkpoint = ctx.checkpoints.get(checkpointId);
|
|
348
|
+
}
|
|
340
349
|
return interrupt;
|
|
341
350
|
}
|
|
342
351
|
const result = await _runPrompt({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { State } from "./state/stateStack.js";
|
|
2
2
|
import { StateStack } from "./state/stateStack.js";
|
|
3
3
|
import type { RuntimeContext } from "./state/context.js";
|
|
4
|
+
import type { SourceLocationOpts } from "./state/checkpointStore.js";
|
|
4
5
|
import type { HandlerFn } from "./types.js";
|
|
5
6
|
/**
|
|
6
7
|
* Runner centralizes step execution logic for generated Agency code.
|
|
@@ -31,6 +32,8 @@ export declare class Runner {
|
|
|
31
32
|
});
|
|
32
33
|
/** The current step path as a string, e.g. "1_0_2" */
|
|
33
34
|
key(): string;
|
|
35
|
+
/** Return checkpoint metadata for the current step. */
|
|
36
|
+
getCheckpointInfo(): SourceLocationOpts;
|
|
34
37
|
private getCounter;
|
|
35
38
|
private setCounter;
|
|
36
39
|
halt(result: any): void;
|
|
@@ -36,6 +36,14 @@ export class Runner {
|
|
|
36
36
|
key() {
|
|
37
37
|
return this.path.join("_");
|
|
38
38
|
}
|
|
39
|
+
/** Return checkpoint metadata for the current step. */
|
|
40
|
+
getCheckpointInfo() {
|
|
41
|
+
return {
|
|
42
|
+
moduleId: this.moduleId,
|
|
43
|
+
scopeName: this.scopeName,
|
|
44
|
+
stepPath: this.path.join("."),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
39
47
|
getCounter() {
|
|
40
48
|
if (this.path.length === 0)
|
|
41
49
|
return this.frame.step;
|
|
@@ -84,6 +92,8 @@ export class Runner {
|
|
|
84
92
|
async maybeDebugHook(id, label = null, isUserAdded = false) {
|
|
85
93
|
if (!this.ctx.debuggerState && !this.ctx.traceWriter)
|
|
86
94
|
return false;
|
|
95
|
+
if (this.ctx.isInsideToolCall())
|
|
96
|
+
return false;
|
|
87
97
|
// On resume after a debug pause, skip the hook.
|
|
88
98
|
// Don't delete the flag yet — step() will clean it up after the
|
|
89
99
|
// callback completes. If the callback halts (nested interrupt),
|
|
@@ -40,6 +40,7 @@ export declare class Checkpoint implements SourceLocation {
|
|
|
40
40
|
get location(): SourceLocation;
|
|
41
41
|
getCurrentFrame(): import("./stateStack.js").StateJSON | undefined;
|
|
42
42
|
getThreadMessages(): ThreadMessages | null;
|
|
43
|
+
private getContentFromMessage;
|
|
43
44
|
getGlobalsForModule(): Record<string, any> | null;
|
|
44
45
|
getFilename(): string;
|
|
45
46
|
pathEquals(other: Checkpoint): boolean;
|
|
@@ -61,16 +61,27 @@ export class Checkpoint {
|
|
|
61
61
|
const messages = activeThread.messages.map((m) => ({
|
|
62
62
|
role: m.role ?? "unknown",
|
|
63
63
|
// smoltalk content can be string, TextPart[], or null
|
|
64
|
-
content:
|
|
65
|
-
? m.content
|
|
66
|
-
: Array.isArray(m.content)
|
|
67
|
-
? m.content.map((p) => p.text ?? "").join("")
|
|
68
|
-
: m.content == null
|
|
69
|
-
? ""
|
|
70
|
-
: String(m.content),
|
|
64
|
+
content: this.getContentFromMessage(m),
|
|
71
65
|
}));
|
|
72
66
|
return { threadId: activeId, messages };
|
|
73
67
|
}
|
|
68
|
+
getContentFromMessage(message) {
|
|
69
|
+
if (typeof message.content === "string") {
|
|
70
|
+
return message.content;
|
|
71
|
+
}
|
|
72
|
+
else if (Array.isArray(message.content)) {
|
|
73
|
+
return message.content.map((part) => part.text ?? "").join("");
|
|
74
|
+
}
|
|
75
|
+
else if (message.content == null) {
|
|
76
|
+
if (message.role === "assistant" && message.toolCalls) {
|
|
77
|
+
return message.toolCalls
|
|
78
|
+
.map((toolCall) => `Tool call: ${toolCall.name}(${JSON.stringify(toolCall.arguments)})`)
|
|
79
|
+
.join("\n");
|
|
80
|
+
}
|
|
81
|
+
return "(no content)";
|
|
82
|
+
}
|
|
83
|
+
return JSON.stringify(message.content);
|
|
84
|
+
}
|
|
74
85
|
getGlobalsForModule() {
|
|
75
86
|
return this.globals.store?.[this.moduleId] ?? null;
|
|
76
87
|
}
|
|
@@ -261,7 +261,7 @@ describe("Checkpoint", () => {
|
|
|
261
261
|
});
|
|
262
262
|
const result = cp.getThreadMessages();
|
|
263
263
|
expect(result.messages).toEqual([
|
|
264
|
-
{ role: "assistant", content: "" },
|
|
264
|
+
{ role: "assistant", content: "(no content)" },
|
|
265
265
|
]);
|
|
266
266
|
});
|
|
267
267
|
it("should concatenate TextPart[] content", () => {
|
|
@@ -23,6 +23,7 @@ export declare class RuntimeContext<T> {
|
|
|
23
23
|
_skipNextCheckpoint: boolean;
|
|
24
24
|
_pendingArgOverrides?: Record<string, any>;
|
|
25
25
|
_restoreCount: number;
|
|
26
|
+
_toolCallDepth: number;
|
|
26
27
|
debuggerState: DebuggerState | null;
|
|
27
28
|
traceWriter: TraceWriter | null;
|
|
28
29
|
statelogClient: StatelogClient;
|
|
@@ -40,6 +41,9 @@ export declare class RuntimeContext<T> {
|
|
|
40
41
|
createExecutionContext(): RuntimeContext<T>;
|
|
41
42
|
pushHandler(fn: HandlerFn): void;
|
|
42
43
|
popHandler(): void;
|
|
44
|
+
enterToolCall(): void;
|
|
45
|
+
exitToolCall(): void;
|
|
46
|
+
isInsideToolCall(): boolean;
|
|
43
47
|
registerClass(name: string, cls: ClassRegistry[string]): void;
|
|
44
48
|
forkStack(): StateStack;
|
|
45
49
|
/** Sever references held by an execution context so GC can reclaim them. */
|