@nightowlsdev/core 0.4.0 → 0.5.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/index.cjs +261 -46
- package/dist/index.d.cts +176 -1
- package/dist/index.d.ts +176 -1
- package/dist/index.js +254 -46
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
ASK_TOOL_NAME: () => ASK_TOOL_NAME,
|
|
26
26
|
AgentMutationForbidden: () => AgentMutationForbidden,
|
|
27
27
|
CapturingExporter: () => CapturingExporter,
|
|
28
|
+
ClientToolError: () => ClientToolError,
|
|
28
29
|
CostGovernor: () => CostGovernor,
|
|
29
30
|
DEFAULT_READ_ONLY_TOOLS: () => import_hooks4.DEFAULT_READ_ONLY_TOOLS,
|
|
30
31
|
DelegateBudgets: () => DelegateBudgets,
|
|
@@ -43,6 +44,7 @@ __export(index_exports, {
|
|
|
43
44
|
allowListModelProvider: () => allowListModelProvider,
|
|
44
45
|
ask: () => import_hooks4.ask,
|
|
45
46
|
assertActorMayMutateDefinition: () => assertActorMayMutateDefinition,
|
|
47
|
+
buildSingleAgentSwarm: () => buildSingleAgentSwarm,
|
|
46
48
|
buildSkillResolver: () => buildSkillResolver,
|
|
47
49
|
composePolicyPrompt: () => composePolicyPrompt,
|
|
48
50
|
composeSystemPrompt: () => composeSystemPrompt,
|
|
@@ -50,11 +52,13 @@ __export(index_exports, {
|
|
|
50
52
|
containerFloor: () => containerFloor,
|
|
51
53
|
createHookDispatcher: () => import_hooks4.createHookDispatcher,
|
|
52
54
|
createInMemoryRateLimitStore: () => createInMemoryRateLimitStore,
|
|
55
|
+
createRunState: () => createRunState,
|
|
53
56
|
customAuth: () => customAuth,
|
|
54
57
|
customTelemetry: () => customTelemetry,
|
|
55
58
|
decideFixedWindow: () => decideFixedWindow,
|
|
56
59
|
defineAgent: () => defineAgent,
|
|
57
60
|
defineBundle: () => defineBundle,
|
|
61
|
+
defineClientTool: () => defineClientTool,
|
|
58
62
|
defineHook: () => import_hooks4.defineHook,
|
|
59
63
|
defineRule: () => defineRule,
|
|
60
64
|
defineSkill: () => defineSkill,
|
|
@@ -62,6 +66,7 @@ __export(index_exports, {
|
|
|
62
66
|
defineTool: () => defineTool,
|
|
63
67
|
defineWorkflow: () => defineWorkflow,
|
|
64
68
|
deny: () => import_hooks4.deny,
|
|
69
|
+
drainTrajectory: () => drainTrajectory,
|
|
65
70
|
ev: () => ev,
|
|
66
71
|
isEvent: () => isEvent,
|
|
67
72
|
isTierSentinel: () => isTierSentinel,
|
|
@@ -70,6 +75,8 @@ __export(index_exports, {
|
|
|
70
75
|
rateConfig: () => rateConfig,
|
|
71
76
|
resolveTelemetry: () => resolveTelemetry,
|
|
72
77
|
resolveTier: () => resolveTier,
|
|
78
|
+
runAgent: () => runAgent,
|
|
79
|
+
runToTrajectory: () => runToTrajectory,
|
|
73
80
|
sumBreakdowns: () => sumBreakdowns,
|
|
74
81
|
sumTurnUsage: () => sumTurnUsage,
|
|
75
82
|
tierModelId: () => tierModelId,
|
|
@@ -188,6 +195,31 @@ function bindSecrets(resolver, ctx) {
|
|
|
188
195
|
};
|
|
189
196
|
}
|
|
190
197
|
|
|
198
|
+
// src/run-state.ts
|
|
199
|
+
var RUN_STATE_KEY = "__nightowls_run_state__";
|
|
200
|
+
function snapshotCopy(store) {
|
|
201
|
+
const plain = Object.fromEntries(store);
|
|
202
|
+
try {
|
|
203
|
+
return (globalThis.structuredClone ?? ((v) => JSON.parse(JSON.stringify(v))))(plain);
|
|
204
|
+
} catch {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.parse(JSON.stringify(plain));
|
|
207
|
+
} catch {
|
|
208
|
+
return plain;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function createRunState(seed) {
|
|
213
|
+
const m = new Map(seed ? Object.entries(seed) : void 0);
|
|
214
|
+
return {
|
|
215
|
+
get: (key) => m.get(key),
|
|
216
|
+
set: (key, value) => void m.set(key, value),
|
|
217
|
+
has: (key) => m.has(key),
|
|
218
|
+
delete: (key) => m.delete(key),
|
|
219
|
+
entries: () => snapshotCopy(m)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
191
223
|
// src/step-driver.ts
|
|
192
224
|
function initialWorkflowState(wf) {
|
|
193
225
|
return { workflow: wf.name, cursor: wf.start, outputs: {}, generationIndex: 0 };
|
|
@@ -1231,13 +1263,14 @@ var SwarmEngine = class {
|
|
|
1231
1263
|
agent() {
|
|
1232
1264
|
return this.mastra.getAgent(AGENT_KEY);
|
|
1233
1265
|
}
|
|
1234
|
-
requestContext(ctx) {
|
|
1266
|
+
requestContext(ctx, state) {
|
|
1235
1267
|
const rc = new import_request_context.RequestContext();
|
|
1236
1268
|
for (const [k, v] of Object.entries(ctx)) {
|
|
1237
1269
|
if (v !== void 0) rc.set(k, v);
|
|
1238
1270
|
}
|
|
1239
1271
|
rc.set(TOOL_GATE_KEY, this.toolGate);
|
|
1240
1272
|
if (this.opts.secrets) rc.set(SECRET_RESOLVER_KEY, this.opts.secrets);
|
|
1273
|
+
if (state) rc.set(RUN_STATE_KEY, state);
|
|
1241
1274
|
return rc;
|
|
1242
1275
|
}
|
|
1243
1276
|
/**
|
|
@@ -1507,6 +1540,9 @@ var SwarmEngine = class {
|
|
|
1507
1540
|
const modelId = this.priceModelId((await this.loadRow(ctx.tenantId, ctx.agentSlug)).modelId ?? "unknown", ctx.tenantId, ctx.agentSlug);
|
|
1508
1541
|
const workflowDef = input.workflow ? this.opts.workflows?.find((w) => w.name === input.workflow) : this.opts.agentWorkflows?.[ctx.agentSlug];
|
|
1509
1542
|
if (input.workflow && !workflowDef) throw new Error(`unknown workflow: ${input.workflow}`);
|
|
1543
|
+
if (this.opts.storage.threads) {
|
|
1544
|
+
await this.opts.storage.threads.ensure({ id: ctx.threadId, orgId: ctx.tenantId, userId: ctx.userId });
|
|
1545
|
+
}
|
|
1510
1546
|
await this.opts.storage.runs.create({
|
|
1511
1547
|
runId: ctx.runId,
|
|
1512
1548
|
tenantId: ctx.tenantId,
|
|
@@ -1524,8 +1560,17 @@ var SwarmEngine = class {
|
|
|
1524
1560
|
const streamed = /* @__PURE__ */ new Set();
|
|
1525
1561
|
const activity = /* @__PURE__ */ new Map();
|
|
1526
1562
|
const turnUsage = [];
|
|
1527
|
-
const
|
|
1563
|
+
const runState = createRunState();
|
|
1564
|
+
const rc = this.requestContext(ctx, runState);
|
|
1528
1565
|
if (this.opts.pageContext) attachPageContext(rc, input.context);
|
|
1566
|
+
let outcome = "failed";
|
|
1567
|
+
if (this.opts.onRunStart) {
|
|
1568
|
+
try {
|
|
1569
|
+
await this.opts.onRunStart(ctx, { input, state: runState });
|
|
1570
|
+
} catch (err) {
|
|
1571
|
+
console.error(`[@nightowlsdev/core] onRunStart threw for run ${ctx.runId}:`, err);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1529
1574
|
const collector = this.opts.telemetry ? new SpanCollector(ctx.runId, () => Date.now(), "run", { agentSlug: ctx.agentSlug }) : null;
|
|
1530
1575
|
let ts = 0;
|
|
1531
1576
|
const emit = async (e) => {
|
|
@@ -1555,7 +1600,7 @@ var SwarmEngine = class {
|
|
|
1555
1600
|
if (workflowDef) {
|
|
1556
1601
|
const wfMessage = input.message.replace(/^(\[user:[^\]]*\]\s*)+/, "");
|
|
1557
1602
|
await emit(ev("swarm.message", base(ctx, ts++), { role: "user", text: wfMessage }));
|
|
1558
|
-
yield* this.driveWorkflow(workflowDef, initialWorkflowState(workflowDef), ctx, { message: wfMessage, context: input.context }, {
|
|
1603
|
+
outcome = yield* this.driveWorkflow(workflowDef, initialWorkflowState(workflowDef), ctx, { message: wfMessage, context: input.context }, {
|
|
1559
1604
|
gov,
|
|
1560
1605
|
modelIdFor,
|
|
1561
1606
|
streamed,
|
|
@@ -1565,7 +1610,11 @@ var SwarmEngine = class {
|
|
|
1565
1610
|
turnUsage,
|
|
1566
1611
|
nextTs: () => ts++,
|
|
1567
1612
|
emit,
|
|
1568
|
-
emitTurn
|
|
1613
|
+
emitTurn,
|
|
1614
|
+
segmentIndex: 0,
|
|
1615
|
+
// FR-004: a run segment starts at generation 0
|
|
1616
|
+
state: runState
|
|
1617
|
+
// FR-003: workflow steps' tools see the run's ctx.state
|
|
1569
1618
|
});
|
|
1570
1619
|
return;
|
|
1571
1620
|
}
|
|
@@ -1593,23 +1642,14 @@ var SwarmEngine = class {
|
|
|
1593
1642
|
const sp = payload.suspendPayload ?? {};
|
|
1594
1643
|
await recordSuspend(this.opts.storage, ctx, followupId, toolCallId);
|
|
1595
1644
|
await this.opts.storage.runs.setStatus(ctx.runId, "suspended");
|
|
1596
|
-
await this.opts.storage.runs.saveSnapshot(ctx.runId, { pending: { toolCallId }, genIndex: generationIndex + 1 });
|
|
1645
|
+
await this.opts.storage.runs.saveSnapshot(ctx.runId, { pending: { toolCallId }, genIndex: generationIndex + 1, state: runState.entries() });
|
|
1597
1646
|
{
|
|
1598
1647
|
const t = await emitTurn();
|
|
1599
1648
|
if (t) yield t;
|
|
1600
1649
|
}
|
|
1601
1650
|
yield await emit(ev("swarm.status", base(ctx, ts++), { state: "waiting" }));
|
|
1602
|
-
yield await emit(
|
|
1603
|
-
|
|
1604
|
-
followupId,
|
|
1605
|
-
toolCallId,
|
|
1606
|
-
to: sp.to ?? "user",
|
|
1607
|
-
from: sp.asker || ctx.agentSlug,
|
|
1608
|
-
// the agent that actually asked (a delegate), for UI attribution
|
|
1609
|
-
prompt: sp.prompt ?? "",
|
|
1610
|
-
field: sp.field
|
|
1611
|
-
})
|
|
1612
|
-
);
|
|
1651
|
+
yield await emit(clientActionOrQuestion(ctx, ts++, followupId, toolCallId, sp));
|
|
1652
|
+
outcome = "suspended";
|
|
1613
1653
|
return;
|
|
1614
1654
|
}
|
|
1615
1655
|
if (part?.type === "error") {
|
|
@@ -1627,7 +1667,7 @@ var SwarmEngine = class {
|
|
|
1627
1667
|
);
|
|
1628
1668
|
return;
|
|
1629
1669
|
}
|
|
1630
|
-
for (const e of mapChunk(part, ctx, gov, modelIdFor, () => ts++, streamed, delegateBudgets, activity, gatesApproval, turnUsage)) {
|
|
1670
|
+
for (const e of mapChunk(part, ctx, gov, modelIdFor, () => ts++, streamed, delegateBudgets, activity, gatesApproval, turnUsage, 0)) {
|
|
1631
1671
|
if (e.type === "swarm.message" || e.type === "swarm.tool_call") {
|
|
1632
1672
|
lastOutputSlug = e.agentSlug;
|
|
1633
1673
|
if (this.opts.verifyCompletion) transcript = appendTranscript(transcript, e);
|
|
@@ -1644,7 +1684,9 @@ var SwarmEngine = class {
|
|
|
1644
1684
|
await this.opts.storage.runs.setStatus(ctx.runId, "suspended");
|
|
1645
1685
|
await this.opts.storage.runs.saveSnapshot(ctx.runId, {
|
|
1646
1686
|
capHit: { message: userMessage, spentUsd: gov.costUsd() },
|
|
1647
|
-
genIndex: generationIndex + 1
|
|
1687
|
+
genIndex: generationIndex + 1,
|
|
1688
|
+
state: runState.entries()
|
|
1689
|
+
// FR-003: persist per-run state across the cap-ask boundary
|
|
1648
1690
|
});
|
|
1649
1691
|
{
|
|
1650
1692
|
const t = await emitTurn();
|
|
@@ -1652,6 +1694,7 @@ var SwarmEngine = class {
|
|
|
1652
1694
|
}
|
|
1653
1695
|
yield await emit(ev("swarm.status", base(ctx, ts++), { state: "waiting" }));
|
|
1654
1696
|
yield await emit(ev("swarm.question", base(ctx, ts++), capQuestion(ctx, followupId, gov)));
|
|
1697
|
+
outcome = "suspended";
|
|
1655
1698
|
return;
|
|
1656
1699
|
}
|
|
1657
1700
|
await this.opts.storage.runs.setStatus(ctx.runId, "failed");
|
|
@@ -1700,6 +1743,7 @@ var SwarmEngine = class {
|
|
|
1700
1743
|
if (t) yield t;
|
|
1701
1744
|
}
|
|
1702
1745
|
yield await emit(ev("swarm.status", base(ctx, ts++), { state: "done" }));
|
|
1746
|
+
outcome = "done";
|
|
1703
1747
|
}
|
|
1704
1748
|
} catch (err) {
|
|
1705
1749
|
const stage = err instanceof ReserveDenied ? "reserve" : "exception";
|
|
@@ -1714,6 +1758,13 @@ var SwarmEngine = class {
|
|
|
1714
1758
|
}
|
|
1715
1759
|
yield await emit(ev("swarm.run_failed", base(ctx, ts++), { stage, message: errMessage(err), retryable: false }));
|
|
1716
1760
|
} finally {
|
|
1761
|
+
if (this.opts.onRunEnd) {
|
|
1762
|
+
try {
|
|
1763
|
+
await this.opts.onRunEnd(ctx, { state: runState, outcome });
|
|
1764
|
+
} catch (err) {
|
|
1765
|
+
console.error(`[@nightowlsdev/core] onRunEnd threw for run ${ctx.runId}:`, err);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1717
1768
|
floorAbort.abort();
|
|
1718
1769
|
await releaseFloor?.();
|
|
1719
1770
|
await exportSpans(this.opts.telemetry, collector);
|
|
@@ -1730,7 +1781,7 @@ var SwarmEngine = class {
|
|
|
1730
1781
|
const driver = new StepDriver(wf, {
|
|
1731
1782
|
nextTs: m.nextTs,
|
|
1732
1783
|
runAgentStep: (slug, message, genIndex) => this.streamWorkflowAgentStep(slug, message, genIndex, ctx, input, m),
|
|
1733
|
-
runToolStep: (toolName, args) => this.runWorkflowToolStep(toolName, args, ctx),
|
|
1784
|
+
runToolStep: (toolName, args) => this.runWorkflowToolStep(toolName, args, ctx, m.state),
|
|
1734
1785
|
saveState: (rid, st) => this.opts.storage.runs.saveSnapshot(rid, { workflow: st })
|
|
1735
1786
|
});
|
|
1736
1787
|
const it = driver.drive(state, ctx, input);
|
|
@@ -1749,7 +1800,7 @@ var SwarmEngine = class {
|
|
|
1749
1800
|
const t = await m.emitTurn();
|
|
1750
1801
|
if (t) yield t;
|
|
1751
1802
|
}
|
|
1752
|
-
return;
|
|
1803
|
+
return "suspended";
|
|
1753
1804
|
}
|
|
1754
1805
|
if (outcome.kind === "failed") {
|
|
1755
1806
|
await this.opts.storage.runs.setStatus(ctx.runId, "failed");
|
|
@@ -1758,7 +1809,7 @@ var SwarmEngine = class {
|
|
|
1758
1809
|
if (t) yield t;
|
|
1759
1810
|
}
|
|
1760
1811
|
yield await m.emit(ev("swarm.run_failed", base(ctx, m.nextTs()), { stage: outcome.stage, message: outcome.message, retryable: true }));
|
|
1761
|
-
return;
|
|
1812
|
+
return "failed";
|
|
1762
1813
|
}
|
|
1763
1814
|
await this.mirrorDelegations(ctx);
|
|
1764
1815
|
await this.attributeRun(ctx);
|
|
@@ -1768,19 +1819,20 @@ var SwarmEngine = class {
|
|
|
1768
1819
|
if (t) yield t;
|
|
1769
1820
|
}
|
|
1770
1821
|
yield await m.emit(ev("swarm.status", base(ctx, m.nextTs()), { state: "done" }));
|
|
1822
|
+
return "done";
|
|
1771
1823
|
}
|
|
1772
1824
|
/** A workflow `agent` step: stream `slug` with `message` (a per-step requestContext so it inherits the agent's
|
|
1773
1825
|
* persona/tools/gate/model), reserving + metering through the caller's machinery, returning the final text. */
|
|
1774
1826
|
async *streamWorkflowAgentStep(slug, message, genIndex, ctx, input, m) {
|
|
1775
1827
|
await this.guardGeneration({ runId: ctx.runId, tenantId: ctx.tenantId, agentSlug: slug, modelId: m.modelIdFor(slug), generationIndex: genIndex, kind: "run" });
|
|
1776
1828
|
const sctx = { ...ctx, agentSlug: slug };
|
|
1777
|
-
const stepRc = this.requestContext(sctx);
|
|
1829
|
+
const stepRc = this.requestContext(sctx, m.state);
|
|
1778
1830
|
if (this.opts.pageContext) attachPageContext(stepRc, input.context);
|
|
1779
1831
|
const result = await this.agent().stream(message, { runId: ctx.runId, requestContext: stepRc, ...this.memoryOpts(sctx) });
|
|
1780
1832
|
let text = "";
|
|
1781
1833
|
for await (const part of result.fullStream) {
|
|
1782
1834
|
if (part?.type === "step-finish") m.gov.step();
|
|
1783
|
-
for (const e of mapChunk(part, sctx, m.gov, m.modelIdFor, m.nextTs, m.streamed, m.delegateBudgets, m.activity, m.gatesApproval, m.turnUsage)) {
|
|
1835
|
+
for (const e of mapChunk(part, sctx, m.gov, m.modelIdFor, m.nextTs, m.streamed, m.delegateBudgets, m.activity, m.gatesApproval, m.turnUsage, m.segmentIndex)) {
|
|
1784
1836
|
if (e.type === "swarm.message") text += e.data.delta ?? e.data.text ?? "";
|
|
1785
1837
|
yield e;
|
|
1786
1838
|
}
|
|
@@ -1788,11 +1840,11 @@ var SwarmEngine = class {
|
|
|
1788
1840
|
return { text };
|
|
1789
1841
|
}
|
|
1790
1842
|
/** A workflow `tool` step: run the gate-free tool body through `executeToolWithGate` (the engine-owned gate). */
|
|
1791
|
-
async runWorkflowToolStep(toolName, args, ctx) {
|
|
1843
|
+
async runWorkflowToolStep(toolName, args, ctx, state) {
|
|
1792
1844
|
const skill = this.opts.resolveSkill?.(toolName);
|
|
1793
1845
|
const exec = skill ? getToolExecutor(skill) : void 0;
|
|
1794
1846
|
if (!skill || !exec) return { ok: false, error: `unknown tool "${toolName}"` };
|
|
1795
|
-
const toolCtx = { tenantId: ctx.tenantId, userId: ctx.userId, runId: ctx.runId, secrets: bindSecrets(this.opts.secrets, ctx) };
|
|
1847
|
+
const toolCtx = { tenantId: ctx.tenantId, userId: ctx.userId, runId: ctx.runId, secrets: bindSecrets(this.opts.secrets, ctx), state };
|
|
1796
1848
|
return executeToolWithGate({
|
|
1797
1849
|
ev: toolPreCallEvent({ runId: ctx.runId, tenantId: ctx.tenantId, agentSlug: ctx.agentSlug, toolName, origin: skill.origin ?? "first-party", needsApproval: this.gatesApproval(toolName), args }),
|
|
1798
1850
|
gate: this.toolGate,
|
|
@@ -1802,6 +1854,7 @@ var SwarmEngine = class {
|
|
|
1802
1854
|
async *resume(args, ctx) {
|
|
1803
1855
|
const snap = await this.opts.storage.runs.loadSnapshot(ctx.tenantId, args.runId);
|
|
1804
1856
|
if (!snap) throw new Error(`no suspended run: ${args.runId}`);
|
|
1857
|
+
const resumedState = createRunState(snap.state ?? void 0);
|
|
1805
1858
|
const generationIndex = typeof snap.genIndex === "number" ? snap.genIndex : 1;
|
|
1806
1859
|
const capHit = snap.capHit;
|
|
1807
1860
|
await this.opts.storage.markFollowupAnswered?.(args.followupId, ctx.tenantId);
|
|
@@ -1835,6 +1888,7 @@ var SwarmEngine = class {
|
|
|
1835
1888
|
turnEmitted = true;
|
|
1836
1889
|
return emit(turnUsageEvent(rctx, ts++, turnUsage, generationIndex));
|
|
1837
1890
|
};
|
|
1891
|
+
let resumeOutcome = "failed";
|
|
1838
1892
|
try {
|
|
1839
1893
|
if (!releaseFloor) {
|
|
1840
1894
|
const held = await this.floor.holder(floorKey);
|
|
@@ -1866,7 +1920,7 @@ var SwarmEngine = class {
|
|
|
1866
1920
|
wfState.outputs[wfState.pending.stepId] = args.answer;
|
|
1867
1921
|
wfState.pending = void 0;
|
|
1868
1922
|
}
|
|
1869
|
-
yield* this.driveWorkflow(wf, wfState, rctx, { message: "", context: args.context }, {
|
|
1923
|
+
resumeOutcome = yield* this.driveWorkflow(wf, wfState, rctx, { message: "", context: args.context }, {
|
|
1870
1924
|
gov,
|
|
1871
1925
|
modelIdFor,
|
|
1872
1926
|
streamed,
|
|
@@ -1876,7 +1930,11 @@ var SwarmEngine = class {
|
|
|
1876
1930
|
turnUsage,
|
|
1877
1931
|
nextTs: () => ts++,
|
|
1878
1932
|
emit,
|
|
1879
|
-
emitTurn
|
|
1933
|
+
emitTurn,
|
|
1934
|
+
segmentIndex: generationIndex,
|
|
1935
|
+
// FR-004: a resume segment starts at the snapshot's genIndex
|
|
1936
|
+
state: resumedState
|
|
1937
|
+
// FR-003: workflow steps' tools see the restored ctx.state
|
|
1880
1938
|
});
|
|
1881
1939
|
return;
|
|
1882
1940
|
}
|
|
@@ -1895,7 +1953,7 @@ var SwarmEngine = class {
|
|
|
1895
1953
|
);
|
|
1896
1954
|
return;
|
|
1897
1955
|
}
|
|
1898
|
-
const rc = this.requestContext({ ...ctx, runId: args.runId });
|
|
1956
|
+
const rc = this.requestContext({ ...ctx, runId: args.runId }, resumedState);
|
|
1899
1957
|
if (this.opts.pageContext) attachPageContext(rc, args.context);
|
|
1900
1958
|
await this.guardGeneration({ runId: args.runId, tenantId: ctx.tenantId, agentSlug: ctx.agentSlug, modelId, generationIndex, kind: "resume" });
|
|
1901
1959
|
let resumeNudges = 0;
|
|
@@ -1927,22 +1985,14 @@ var SwarmEngine = class {
|
|
|
1927
1985
|
const sp = payload.suspendPayload ?? {};
|
|
1928
1986
|
await recordSuspend(this.opts.storage, rctx, followupId, toolCallId);
|
|
1929
1987
|
await this.opts.storage.runs.setStatus(args.runId, "suspended");
|
|
1930
|
-
await this.opts.storage.runs.saveSnapshot(args.runId, { pending: { toolCallId }, genIndex: generationIndex + 1 });
|
|
1988
|
+
await this.opts.storage.runs.saveSnapshot(args.runId, { pending: { toolCallId }, genIndex: generationIndex + 1, state: resumedState.entries() });
|
|
1931
1989
|
{
|
|
1932
1990
|
const t = await emitTurn();
|
|
1933
1991
|
if (t) yield t;
|
|
1934
1992
|
}
|
|
1935
1993
|
yield await emit(ev("swarm.status", base(rctx, ts++), { state: "waiting" }));
|
|
1936
|
-
yield await emit(
|
|
1937
|
-
|
|
1938
|
-
followupId,
|
|
1939
|
-
toolCallId,
|
|
1940
|
-
to: sp.to ?? "user",
|
|
1941
|
-
from: sp.asker || rctx.agentSlug,
|
|
1942
|
-
prompt: sp.prompt ?? "",
|
|
1943
|
-
field: sp.field
|
|
1944
|
-
})
|
|
1945
|
-
);
|
|
1994
|
+
yield await emit(clientActionOrQuestion(rctx, ts++, followupId, toolCallId, sp));
|
|
1995
|
+
resumeOutcome = "suspended";
|
|
1946
1996
|
return;
|
|
1947
1997
|
}
|
|
1948
1998
|
if (part?.type === "error") {
|
|
@@ -1961,7 +2011,7 @@ var SwarmEngine = class {
|
|
|
1961
2011
|
return;
|
|
1962
2012
|
}
|
|
1963
2013
|
collectSpans(collector, part, modelId, gov);
|
|
1964
|
-
for (const e of mapChunk(part, rctx, gov, modelIdFor, () => ts++, streamed, delegateBudgets, activity, gatesApproval, turnUsage)) {
|
|
2014
|
+
for (const e of mapChunk(part, rctx, gov, modelIdFor, () => ts++, streamed, delegateBudgets, activity, gatesApproval, turnUsage, generationIndex)) {
|
|
1965
2015
|
if (e.type === "swarm.message" || e.type === "swarm.tool_call") {
|
|
1966
2016
|
lastOutputSlug = e.agentSlug;
|
|
1967
2017
|
if (this.opts.verifyCompletion) transcript = appendTranscript(transcript, e);
|
|
@@ -1977,7 +2027,9 @@ var SwarmEngine = class {
|
|
|
1977
2027
|
await this.opts.storage.runs.setStatus(args.runId, "suspended");
|
|
1978
2028
|
await this.opts.storage.runs.saveSnapshot(args.runId, {
|
|
1979
2029
|
capHit: { message: capHit.message, spentUsd: gov.costUsd() },
|
|
1980
|
-
genIndex: generationIndex + 1
|
|
2030
|
+
genIndex: generationIndex + 1,
|
|
2031
|
+
state: resumedState.entries()
|
|
2032
|
+
// FR-003: persist per-run state across the cap-ask boundary
|
|
1981
2033
|
});
|
|
1982
2034
|
{
|
|
1983
2035
|
const t = await emitTurn();
|
|
@@ -1985,6 +2037,7 @@ var SwarmEngine = class {
|
|
|
1985
2037
|
}
|
|
1986
2038
|
yield await emit(ev("swarm.status", base(rctx, ts++), { state: "waiting" }));
|
|
1987
2039
|
yield await emit(ev("swarm.question", base(rctx, ts++), capQuestion(rctx, followupId, gov)));
|
|
2040
|
+
resumeOutcome = "suspended";
|
|
1988
2041
|
return;
|
|
1989
2042
|
}
|
|
1990
2043
|
await this.opts.storage.runs.setStatus(args.runId, "failed");
|
|
@@ -2031,6 +2084,7 @@ var SwarmEngine = class {
|
|
|
2031
2084
|
if (t) yield t;
|
|
2032
2085
|
}
|
|
2033
2086
|
yield await emit(ev("swarm.status", base(rctx, ts++), { state: "done" }));
|
|
2087
|
+
resumeOutcome = "done";
|
|
2034
2088
|
}
|
|
2035
2089
|
} catch (err) {
|
|
2036
2090
|
const stage = err instanceof ReserveDenied ? "reserve" : "exception";
|
|
@@ -2045,6 +2099,13 @@ var SwarmEngine = class {
|
|
|
2045
2099
|
}
|
|
2046
2100
|
yield await emit(ev("swarm.run_failed", base(rctx, ts++), { stage, message: errMessage(err), retryable: false }));
|
|
2047
2101
|
} finally {
|
|
2102
|
+
if (this.opts.onRunEnd) {
|
|
2103
|
+
try {
|
|
2104
|
+
await this.opts.onRunEnd(ctx, { state: resumedState, outcome: resumeOutcome });
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
console.error(`[@nightowlsdev/core] onRunEnd threw for resume ${args.runId}:`, err);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2048
2109
|
floorAbort.abort();
|
|
2049
2110
|
await releaseFloor?.();
|
|
2050
2111
|
await exportSpans(this.opts.telemetry, collector);
|
|
@@ -2064,6 +2125,26 @@ function errMessage(err) {
|
|
|
2064
2125
|
function base(ctx, ts) {
|
|
2065
2126
|
return { runId: ctx.runId, agentSlug: ctx.agentSlug, ts };
|
|
2066
2127
|
}
|
|
2128
|
+
function clientActionOrQuestion(ctx, ts, followupId, toolCallId, sp) {
|
|
2129
|
+
if (sp.clientAction) {
|
|
2130
|
+
return ev("swarm.client_action", base(ctx, ts), {
|
|
2131
|
+
followupId,
|
|
2132
|
+
toolCallId,
|
|
2133
|
+
tool: sp.clientAction.tool,
|
|
2134
|
+
input: sp.clientAction.input,
|
|
2135
|
+
needsApproval: sp.clientAction.needsApproval ?? false,
|
|
2136
|
+
from: sp.asker || ctx.agentSlug
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
return ev("swarm.question", base(ctx, ts), {
|
|
2140
|
+
followupId,
|
|
2141
|
+
toolCallId,
|
|
2142
|
+
to: sp.to ?? "user",
|
|
2143
|
+
from: sp.asker || ctx.agentSlug,
|
|
2144
|
+
prompt: sp.prompt ?? "",
|
|
2145
|
+
field: sp.field
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2067
2148
|
var CAP_FOLLOWUP_SUFFIX = "cap";
|
|
2068
2149
|
function capQuestion(ctx, followupId, gov) {
|
|
2069
2150
|
const spent = gov.costUsd();
|
|
@@ -2159,7 +2240,7 @@ async function recordSuspend(storage, ctx, followupId, toolCallId) {
|
|
|
2159
2240
|
}
|
|
2160
2241
|
await storage.recordSuspend?.(ctx.runId, ctx.tenantId, followupId, toolCallId);
|
|
2161
2242
|
}
|
|
2162
|
-
function mapChunk(part, ctx, gov, modelIdFor, nextTs, streamed, delegateBudgets, activity, gatesApproval, turnUsage) {
|
|
2243
|
+
function mapChunk(part, ctx, gov, modelIdFor, nextTs, streamed, delegateBudgets, activity, gatesApproval, turnUsage, segmentIndex) {
|
|
2163
2244
|
const p = part.payload ?? {};
|
|
2164
2245
|
const modelId = modelIdFor(ctx.agentSlug);
|
|
2165
2246
|
const act = (slug) => {
|
|
@@ -2229,8 +2310,9 @@ function mapChunk(part, ctx, gov, modelIdFor, nextTs, streamed, delegateBudgets,
|
|
|
2229
2310
|
gov.addUsage(modelId, u);
|
|
2230
2311
|
delegateBudgets?.addUsage(ctx.agentSlug, modelId, u);
|
|
2231
2312
|
const cost = gov.costOf(modelId, u);
|
|
2313
|
+
const generationId = `${ctx.runId}:${segmentIndex}:${turnUsage.length}`;
|
|
2232
2314
|
turnUsage.push({ slug: ctx.agentSlug, breakdown: u, cost });
|
|
2233
|
-
return [ev("swarm.usage", base(ctx, nextTs()), { slug: ctx.agentSlug, modelId, breakdown: u, cost })];
|
|
2315
|
+
return [ev("swarm.usage", base(ctx, nextTs()), { slug: ctx.agentSlug, modelId, breakdown: u, cost, generationId })];
|
|
2234
2316
|
}
|
|
2235
2317
|
return [];
|
|
2236
2318
|
}
|
|
@@ -2241,7 +2323,7 @@ function mapChunk(part, ctx, gov, modelIdFor, nextTs, streamed, delegateBudgets,
|
|
|
2241
2323
|
const inner = p.output;
|
|
2242
2324
|
if (!inner || typeof inner.type !== "string") return [];
|
|
2243
2325
|
if (inner.type === "text-delta") streamed.add(p.toolCallId);
|
|
2244
|
-
return mapChunk(inner, { ...ctx, agentSlug: subSlug }, gov, modelIdFor, nextTs, streamed, delegateBudgets, activity, gatesApproval, turnUsage);
|
|
2326
|
+
return mapChunk(inner, { ...ctx, agentSlug: subSlug }, gov, modelIdFor, nextTs, streamed, delegateBudgets, activity, gatesApproval, turnUsage, segmentIndex);
|
|
2245
2327
|
}
|
|
2246
2328
|
case "tool-error": {
|
|
2247
2329
|
const name = p.toolName ?? "";
|
|
@@ -2434,7 +2516,11 @@ function defineTool(spec) {
|
|
|
2434
2516
|
tenantId,
|
|
2435
2517
|
userId,
|
|
2436
2518
|
runId,
|
|
2437
|
-
secrets: bindSecrets(resolver, scopedCtx)
|
|
2519
|
+
secrets: bindSecrets(resolver, scopedCtx),
|
|
2520
|
+
// FR-003: the per-run state handle the engine put on the rc (same object across the run's tool calls +
|
|
2521
|
+
// delegated sub-agents). Absent on a raw test stream built without the engine — then `ctx.state` is
|
|
2522
|
+
// undefined, unchanged from prior behaviour.
|
|
2523
|
+
state: rc?.get?.(RUN_STATE_KEY)
|
|
2438
2524
|
};
|
|
2439
2525
|
const run = () => spec.execute(inputData, ctx);
|
|
2440
2526
|
const agentCtx = context?.agent;
|
|
@@ -2476,6 +2562,48 @@ function deriveAsker(agentCtx, rc) {
|
|
|
2476
2562
|
if (agentId.startsWith("swarm-sub-")) return agentId.slice("swarm-sub-".length);
|
|
2477
2563
|
return rc?.get?.("agentSlug") ?? "";
|
|
2478
2564
|
}
|
|
2565
|
+
var CLIENT_ACTION_SUSPEND_SCHEMA = import_zod4.z.object({
|
|
2566
|
+
clientAction: import_zod4.z.object({ tool: import_zod4.z.string(), input: import_zod4.z.any(), needsApproval: import_zod4.z.boolean().optional() }),
|
|
2567
|
+
asker: import_zod4.z.string().optional()
|
|
2568
|
+
});
|
|
2569
|
+
var CLIENT_ACTION_RESUME_SCHEMA = import_zod4.z.object({ answer: import_zod4.z.any() });
|
|
2570
|
+
var ClientToolError = class extends Error {
|
|
2571
|
+
constructor(toolName, reason) {
|
|
2572
|
+
super(reason ? `client tool ${toolName} failed: ${reason}` : `client tool ${toolName} failed`);
|
|
2573
|
+
this.name = "ClientToolError";
|
|
2574
|
+
}
|
|
2575
|
+
};
|
|
2576
|
+
function defineClientTool(spec) {
|
|
2577
|
+
const needsApproval = spec.needsApproval ?? false;
|
|
2578
|
+
const mastraTool = (0, import_tools4.createTool)({
|
|
2579
|
+
id: spec.name,
|
|
2580
|
+
description: spec.description ?? spec.name,
|
|
2581
|
+
inputSchema: spec.inputSchema,
|
|
2582
|
+
outputSchema: spec.outputSchema,
|
|
2583
|
+
suspendSchema: CLIENT_ACTION_SUSPEND_SCHEMA,
|
|
2584
|
+
resumeSchema: CLIENT_ACTION_RESUME_SCHEMA,
|
|
2585
|
+
execute: async (inputData, context) => {
|
|
2586
|
+
const rc = context?.requestContext;
|
|
2587
|
+
const agentCtx = context?.agent;
|
|
2588
|
+
if (agentCtx?.resumeData !== void 0 && agentCtx.resumeData !== null) {
|
|
2589
|
+
const answer = agentCtx.resumeData.answer;
|
|
2590
|
+
if (answer && typeof answer === "object" && "error" in answer && answer.error) {
|
|
2591
|
+
throw new ClientToolError(spec.name, String(answer.error));
|
|
2592
|
+
}
|
|
2593
|
+
return answer && typeof answer === "object" && "output" in answer ? answer.output : answer;
|
|
2594
|
+
}
|
|
2595
|
+
if (typeof agentCtx?.suspend !== "function") {
|
|
2596
|
+
throw new ClientToolError(spec.name, "client tools require an agent-driven run (no server execute)");
|
|
2597
|
+
}
|
|
2598
|
+
const asker = deriveAsker(agentCtx, rc);
|
|
2599
|
+
await agentCtx.suspend({ clientAction: { tool: spec.name, input: inputData, needsApproval }, asker });
|
|
2600
|
+
throw new ClientToolError(spec.name, "awaiting client action");
|
|
2601
|
+
}
|
|
2602
|
+
});
|
|
2603
|
+
const handle = { name: spec.name, needsApproval, origin: "first-party" };
|
|
2604
|
+
MASTRA.set(handle, mastraTool);
|
|
2605
|
+
return handle;
|
|
2606
|
+
}
|
|
2479
2607
|
function __getMastraTool(t) {
|
|
2480
2608
|
return MASTRA.get(t);
|
|
2481
2609
|
}
|
|
@@ -2802,7 +2930,10 @@ function defineSwarm(cfg) {
|
|
|
2802
2930
|
secrets: cfg.secrets,
|
|
2803
2931
|
// SP3: best-effort per-event observer (metering debit/settle) — transport-agnostic, fired in run + resume.
|
|
2804
2932
|
onEvent: cfg.onEvent,
|
|
2805
|
-
verifyCompletion: cfg.verifyCompletion
|
|
2933
|
+
verifyCompletion: cfg.verifyCompletion,
|
|
2934
|
+
// FR-003: per-run lifecycle hooks (seed `ctx.state` at run start, drain at run end).
|
|
2935
|
+
onRunStart: cfg.onRunStart,
|
|
2936
|
+
onRunEnd: cfg.onRunEnd
|
|
2806
2937
|
});
|
|
2807
2938
|
return { engine };
|
|
2808
2939
|
}
|
|
@@ -2826,6 +2957,8 @@ var InMemoryStorage = class {
|
|
|
2826
2957
|
heads = /* @__PURE__ */ new Map();
|
|
2827
2958
|
// key: tenantId:slug -> version
|
|
2828
2959
|
pads = /* @__PURE__ */ new Map();
|
|
2960
|
+
threadRows = /* @__PURE__ */ new Map();
|
|
2961
|
+
// FR-009
|
|
2829
2962
|
seedAgent(v, tenantId = "default") {
|
|
2830
2963
|
this.agentRows.set(`${tenantId}:${v.slug}:${v.version}`, v);
|
|
2831
2964
|
this.heads.set(`${tenantId}:${v.slug}`, v.version);
|
|
@@ -2909,6 +3042,18 @@ var InMemoryStorage = class {
|
|
|
2909
3042
|
},
|
|
2910
3043
|
getWaitpoint: async (followupId) => this.waitpoints.get(followupId) ?? null
|
|
2911
3044
|
};
|
|
3045
|
+
// FR-009: idempotent thread-row creation. The dev store has no FK, so `messages.append` never threw `unknown
|
|
3046
|
+
// thread` here — this implements the contract so the engine's run-start ensure works against the in-memory store
|
|
3047
|
+
// too, and a host can read back the recorded thread (getThread) in tests.
|
|
3048
|
+
threads = {
|
|
3049
|
+
ensure: async ({ id, orgId, userId, projectId }) => {
|
|
3050
|
+
if (!this.threadRows.has(id)) this.threadRows.set(id, { id, orgId, userId, projectId });
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
/** Test/host helper: read a recorded thread row. */
|
|
3054
|
+
getThread(id) {
|
|
3055
|
+
return this.threadRows.get(id);
|
|
3056
|
+
}
|
|
2912
3057
|
messages = {
|
|
2913
3058
|
append: async (m) => {
|
|
2914
3059
|
this.msgs.push(m);
|
|
@@ -2948,6 +3093,69 @@ var InMemoryStorage = class {
|
|
|
2948
3093
|
};
|
|
2949
3094
|
};
|
|
2950
3095
|
|
|
3096
|
+
// src/run-agent.ts
|
|
3097
|
+
function uid() {
|
|
3098
|
+
return globalThis.crypto?.randomUUID?.() ?? `id-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
|
|
3099
|
+
}
|
|
3100
|
+
async function drainTrajectory(stream) {
|
|
3101
|
+
const events = [];
|
|
3102
|
+
let output = "";
|
|
3103
|
+
for await (const e of stream) {
|
|
3104
|
+
events.push(e);
|
|
3105
|
+
if (e.type === "swarm.message") {
|
|
3106
|
+
const d = e.data;
|
|
3107
|
+
if (d.role === "assistant") output += d.delta ?? d.text ?? "";
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
return { events, output };
|
|
3111
|
+
}
|
|
3112
|
+
async function runToTrajectory(target, input, ctx) {
|
|
3113
|
+
const engine = "engine" in target ? target.engine : target;
|
|
3114
|
+
const runInput = typeof input === "string" ? { message: input } : input;
|
|
3115
|
+
const full = ephemeralCtx(ctx?.agentSlug ?? "agent", ctx);
|
|
3116
|
+
return drainTrajectory(engine.run(runInput, full));
|
|
3117
|
+
}
|
|
3118
|
+
function ephemeralCtx(agentSlug, over) {
|
|
3119
|
+
return {
|
|
3120
|
+
tenantId: over?.tenantId ?? "default",
|
|
3121
|
+
userId: over?.userId ?? "local",
|
|
3122
|
+
agentSlug: over?.agentSlug ?? agentSlug,
|
|
3123
|
+
runId: over?.runId ?? uid(),
|
|
3124
|
+
threadId: over?.threadId ?? uid(),
|
|
3125
|
+
...over?.agentVersion !== void 0 ? { agentVersion: over.agentVersion } : {}
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
function buildSingleAgentSwarm(def, opts) {
|
|
3129
|
+
const t = opts.models?.tier;
|
|
3130
|
+
const tierAllow = t ? [t.tiers.swift, ...t.tiers.genius ? [t.tiers.genius] : []] : [];
|
|
3131
|
+
const allow = opts.models?.allow ?? [def.head.modelId, ...tierAllow];
|
|
3132
|
+
const storage = opts.storage ?? new InMemoryStorage();
|
|
3133
|
+
const cfg = {
|
|
3134
|
+
storage,
|
|
3135
|
+
agents: [def],
|
|
3136
|
+
models: { allow, ...opts.models?.tier ? { tier: opts.models.tier } : {} },
|
|
3137
|
+
modelFactory: opts.modelFactory,
|
|
3138
|
+
cost: { maxSteps: 50, maxCostUsd: 10, ...opts.cost },
|
|
3139
|
+
...opts.telemetry !== void 0 ? { telemetry: opts.telemetry } : {},
|
|
3140
|
+
...opts.memory !== void 0 ? { memory: opts.memory } : {},
|
|
3141
|
+
...opts.hooks !== void 0 ? { hooks: opts.hooks } : {},
|
|
3142
|
+
...opts.toolApproval !== void 0 ? { toolApproval: opts.toolApproval } : {},
|
|
3143
|
+
...opts.secrets !== void 0 ? { secrets: opts.secrets } : {},
|
|
3144
|
+
...opts.onEvent !== void 0 ? { onEvent: opts.onEvent } : {},
|
|
3145
|
+
...opts.onRunStart !== void 0 ? { onRunStart: opts.onRunStart } : {},
|
|
3146
|
+
...opts.onRunEnd !== void 0 ? { onRunEnd: opts.onRunEnd } : {},
|
|
3147
|
+
...opts.pageContext !== void 0 ? { pageContext: opts.pageContext } : {},
|
|
3148
|
+
...opts.mastraStore !== void 0 ? { mastraStore: opts.mastraStore } : {}
|
|
3149
|
+
};
|
|
3150
|
+
return defineSwarm(cfg);
|
|
3151
|
+
}
|
|
3152
|
+
async function runAgent(def, input, opts) {
|
|
3153
|
+
const swarm = buildSingleAgentSwarm(def, opts);
|
|
3154
|
+
const runInput = typeof input === "string" ? { message: input } : input;
|
|
3155
|
+
const ctx = ephemeralCtx(def.head.slug, { ...opts.ctx, agentSlug: def.head.slug });
|
|
3156
|
+
return drainTrajectory(swarm.engine.run(runInput, ctx));
|
|
3157
|
+
}
|
|
3158
|
+
|
|
2951
3159
|
// src/auth.ts
|
|
2952
3160
|
var customAuth = (fn) => ({ authenticate: fn });
|
|
2953
3161
|
|
|
@@ -2988,6 +3196,7 @@ var VERSION = "0.0.0";
|
|
|
2988
3196
|
ASK_TOOL_NAME,
|
|
2989
3197
|
AgentMutationForbidden,
|
|
2990
3198
|
CapturingExporter,
|
|
3199
|
+
ClientToolError,
|
|
2991
3200
|
CostGovernor,
|
|
2992
3201
|
DEFAULT_READ_ONLY_TOOLS,
|
|
2993
3202
|
DelegateBudgets,
|
|
@@ -3006,6 +3215,7 @@ var VERSION = "0.0.0";
|
|
|
3006
3215
|
allowListModelProvider,
|
|
3007
3216
|
ask,
|
|
3008
3217
|
assertActorMayMutateDefinition,
|
|
3218
|
+
buildSingleAgentSwarm,
|
|
3009
3219
|
buildSkillResolver,
|
|
3010
3220
|
composePolicyPrompt,
|
|
3011
3221
|
composeSystemPrompt,
|
|
@@ -3013,11 +3223,13 @@ var VERSION = "0.0.0";
|
|
|
3013
3223
|
containerFloor,
|
|
3014
3224
|
createHookDispatcher,
|
|
3015
3225
|
createInMemoryRateLimitStore,
|
|
3226
|
+
createRunState,
|
|
3016
3227
|
customAuth,
|
|
3017
3228
|
customTelemetry,
|
|
3018
3229
|
decideFixedWindow,
|
|
3019
3230
|
defineAgent,
|
|
3020
3231
|
defineBundle,
|
|
3232
|
+
defineClientTool,
|
|
3021
3233
|
defineHook,
|
|
3022
3234
|
defineRule,
|
|
3023
3235
|
defineSkill,
|
|
@@ -3025,6 +3237,7 @@ var VERSION = "0.0.0";
|
|
|
3025
3237
|
defineTool,
|
|
3026
3238
|
defineWorkflow,
|
|
3027
3239
|
deny,
|
|
3240
|
+
drainTrajectory,
|
|
3028
3241
|
ev,
|
|
3029
3242
|
isEvent,
|
|
3030
3243
|
isTierSentinel,
|
|
@@ -3033,6 +3246,8 @@ var VERSION = "0.0.0";
|
|
|
3033
3246
|
rateConfig,
|
|
3034
3247
|
resolveTelemetry,
|
|
3035
3248
|
resolveTier,
|
|
3249
|
+
runAgent,
|
|
3250
|
+
runToTrajectory,
|
|
3036
3251
|
sumBreakdowns,
|
|
3037
3252
|
sumTurnUsage,
|
|
3038
3253
|
tierModelId,
|