@kuralle-agents/core 0.12.0 → 0.14.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/README.md +89 -4
- package/dist/ai-sdk/uiMessageStream.d.ts +2 -2
- package/dist/ai-sdk/uiMessageStream.js +42 -29
- package/dist/capabilities/LivePromptAssembler.d.ts +1 -1
- package/dist/capabilities/LivePromptAssembler.js +2 -1
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/eval/EvalRunner.js +4 -4
- package/dist/eval/simulation.js +4 -3
- package/dist/events/TurnHandle.d.ts +3 -3
- package/dist/flow/collectDigression.d.ts +5 -2
- package/dist/flow/collectDigression.js +48 -10
- package/dist/flow/collectUntilComplete.js +13 -5
- package/dist/flow/degrade.js +8 -4
- package/dist/flow/emitInteractive.d.ts +2 -2
- package/dist/flow/emitInteractive.js +6 -3
- package/dist/flow/extraction.d.ts +3 -2
- package/dist/flow/extraction.js +14 -4
- package/dist/flow/reduceTransition.d.ts +2 -2
- package/dist/flow/reduceTransition.js +7 -3
- package/dist/flow/runFlow.js +97 -17
- package/dist/foundation/AgentStateController.d.ts +1 -1
- package/dist/foundation/ToolExecutor.d.ts +2 -2
- package/dist/foundation/index.d.ts +0 -11
- package/dist/foundation/index.js +0 -5
- package/dist/index.d.ts +15 -15
- package/dist/index.js +9 -6
- package/dist/outcomes/streamPart.d.ts +2 -2
- package/dist/outcomes/streamPart.js +2 -3
- package/dist/prompts/PromptBuilder.d.ts +3 -3
- package/dist/prompts/PromptBuilder.js +13 -6
- package/dist/prompts/index.d.ts +1 -1
- package/dist/prompts/types.d.ts +9 -2
- package/dist/runtime/InMemoryRetrievalCache.d.ts +25 -0
- package/dist/runtime/InMemoryRetrievalCache.js +59 -0
- package/dist/runtime/KnowledgeProvider.d.ts +3 -2
- package/dist/runtime/KnowledgeProvider.js +60 -31
- package/dist/runtime/Runtime.d.ts +47 -1
- package/dist/runtime/Runtime.js +273 -36
- package/dist/runtime/TokenAccumulator.d.ts +7 -0
- package/dist/runtime/TokenAccumulator.js +7 -0
- package/dist/runtime/TraceRecorder.d.ts +51 -0
- package/dist/runtime/TraceRecorder.js +326 -0
- package/dist/runtime/buildAgentToolSurface.js +1 -1
- package/dist/runtime/channels/TextDriver.js +57 -31
- package/dist/runtime/channels/executeModelTool.d.ts +14 -1
- package/dist/runtime/channels/executeModelTool.js +132 -3
- package/dist/runtime/channels/extractionTurn.js +1 -1
- package/dist/runtime/channels/index.d.ts +0 -3
- package/dist/runtime/channels/index.js +0 -6
- package/dist/runtime/channels/inputBuffer.d.ts +1 -0
- package/dist/runtime/channels/inputBuffer.js +9 -0
- package/dist/runtime/channels/streaming/hostControlSpeak.js +5 -1
- package/dist/runtime/channels/streaming/speakGated.js +24 -12
- package/dist/runtime/citations/index.d.ts +3 -3
- package/dist/runtime/closeRun.js +14 -10
- package/dist/runtime/compaction.d.ts +2 -0
- package/dist/runtime/compaction.js +3 -2
- package/dist/runtime/controlFlowSignal.d.ts +24 -0
- package/dist/runtime/controlFlowSignal.js +29 -0
- package/dist/runtime/ctx.d.ts +3 -4
- package/dist/runtime/ctx.js +147 -60
- package/dist/runtime/durable/RunStore.d.ts +16 -0
- package/dist/runtime/durable/RunStore.js +7 -1
- package/dist/runtime/durable/SessionRunStore.d.ts +7 -2
- package/dist/runtime/durable/SessionRunStore.js +136 -34
- package/dist/runtime/durable/idempotency.d.ts +1 -0
- package/dist/runtime/durable/idempotency.js +3 -0
- package/dist/runtime/durable/replay.js +7 -2
- package/dist/runtime/durable/types.d.ts +11 -0
- package/dist/runtime/goals.d.ts +18 -0
- package/dist/runtime/goals.js +158 -0
- package/dist/runtime/grounding/gather.d.ts +1 -1
- package/dist/runtime/grounding/inMemoryKnowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.d.ts +1 -1
- package/dist/runtime/grounding/knowledge.js +1 -1
- package/dist/runtime/handoffContinuation.d.ts +20 -0
- package/dist/runtime/handoffContinuation.js +30 -0
- package/dist/runtime/handoffOscillation.d.ts +6 -0
- package/dist/runtime/handoffOscillation.js +17 -0
- package/dist/runtime/hostLoop.js +37 -6
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/openRun.d.ts +2 -0
- package/dist/runtime/openRun.js +46 -17
- package/dist/runtime/outcomeMarking.d.ts +2 -2
- package/dist/runtime/outcomeMarking.js +2 -1
- package/dist/runtime/policies/agentTurn.d.ts +1 -1
- package/dist/runtime/policies/limits.d.ts +1 -0
- package/dist/runtime/policies/limits.js +3 -0
- package/dist/runtime/runHookSafely.d.ts +3 -0
- package/dist/runtime/runHookSafely.js +19 -0
- package/dist/runtime/select.d.ts +5 -1
- package/dist/runtime/select.js +30 -1
- package/dist/runtime/turnTokenUsage.d.ts +28 -0
- package/dist/runtime/turnTokenUsage.js +70 -0
- package/dist/scheduler/index.d.ts +2 -2
- package/dist/scheduler/index.js +1 -1
- package/dist/services/TracingService.js +2 -3
- package/dist/session/SessionStore.d.ts +6 -0
- package/dist/session/SessionStore.js +12 -1
- package/dist/session/stores/MemoryStore.d.ts +1 -1
- package/dist/session/stores/MemoryStore.js +16 -2
- package/dist/session/testing.d.ts +6 -1
- package/dist/session/testing.js +34 -0
- package/dist/session/utils.d.ts +3 -0
- package/dist/session/utils.js +23 -0
- package/dist/skills/collectSkills.d.ts +11 -3
- package/dist/skills/collectSkills.js +51 -8
- package/dist/skills/compositeSkillStore.d.ts +26 -0
- package/dist/skills/compositeSkillStore.js +54 -0
- package/dist/skills/defineSkill.d.ts +21 -0
- package/dist/skills/defineSkill.js +26 -0
- package/dist/skills/fsSkillStore.d.ts +3 -0
- package/dist/skills/fsSkillStore.js +79 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/parseSkillFrontmatter.d.ts +14 -0
- package/dist/skills/parseSkillFrontmatter.js +183 -0
- package/dist/skills/wireAgentSkills.d.ts +2 -1
- package/dist/skills/wireAgentSkills.js +2 -2
- package/dist/testing/mocks.d.ts +3 -3
- package/dist/testing/mocks.js +7 -3
- package/dist/tools/controlResults.d.ts +14 -0
- package/dist/tools/controlResults.js +15 -0
- package/dist/tools/effect/ToolExecutor.d.ts +6 -0
- package/dist/tools/effect/ToolExecutor.js +75 -36
- package/dist/tools/effect/defineTool.d.ts +11 -0
- package/dist/tools/effect/defineTool.js +3 -0
- package/dist/tools/effect/errors.d.ts +2 -5
- package/dist/tools/effect/errors.js +2 -5
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/tracing/MemoryTraceStore.d.ts +16 -0
- package/dist/tracing/MemoryTraceStore.js +56 -0
- package/dist/tracing/OtelTraceSink.d.ts +127 -0
- package/dist/tracing/OtelTraceSink.js +101 -0
- package/dist/tracing/TraceStore.d.ts +19 -0
- package/dist/tracing/TraceStore.js +32 -0
- package/dist/tracing/index.d.ts +3 -0
- package/dist/tracing/index.js +3 -0
- package/dist/tracing/testing.d.ts +3 -0
- package/dist/tracing/testing.js +45 -0
- package/dist/types/agentConfig.d.ts +2 -1
- package/dist/types/channel.d.ts +14 -1
- package/dist/types/effectTool.d.ts +6 -0
- package/dist/types/flow.d.ts +1 -1
- package/dist/types/guardrails.d.ts +5 -0
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/index.d.ts +25 -15
- package/dist/types/index.js +8 -12
- package/dist/types/knowledge.d.ts +213 -0
- package/dist/types/processors.d.ts +0 -58
- package/dist/types/run-context.d.ts +16 -7
- package/dist/types/runtime.d.ts +3 -74
- package/dist/types/session.d.ts +11 -1
- package/dist/types/skills.d.ts +15 -1
- package/dist/types/stream.d.ts +138 -68
- package/dist/types/stream.js +34 -1
- package/dist/types/trace.d.ts +52 -0
- package/guides/AGENTS.md +3 -3
- package/guides/EXAMPLE_VERIFICATION.md +4 -4
- package/guides/FLOWS.md +1 -1
- package/guides/GETTING_STARTED.md +1 -1
- package/guides/RUNTIME.md +20 -15
- package/guides/TOOLS.md +1 -1
- package/package.json +13 -14
- package/dist/foundation/AgentDefinition.d.ts +0 -17
- package/dist/foundation/ConversationEventLog.d.ts +0 -71
- package/dist/foundation/ConversationState.d.ts +0 -33
- package/dist/foundation/ConversationState.js +0 -1
- package/dist/foundation/DefaultConversationEventLog.d.ts +0 -27
- package/dist/foundation/DefaultConversationEventLog.js +0 -205
- package/dist/foundation/DefaultConversationState.d.ts +0 -36
- package/dist/foundation/DefaultConversationState.js +0 -103
- package/dist/foundation/DefaultToolExecutor.d.ts +0 -57
- package/dist/foundation/DefaultToolExecutor.js +0 -132
- package/dist/foundation/createFoundation.d.ts +0 -32
- package/dist/foundation/createFoundation.js +0 -33
- package/dist/hooks/HookRunner.d.ts +0 -37
- package/dist/hooks/HookRunner.js +0 -111
- package/dist/hooks/builtin/logging.d.ts +0 -5
- package/dist/hooks/builtin/logging.js +0 -98
- package/dist/hooks/builtin/metrics.d.ts +0 -7
- package/dist/hooks/builtin/metrics.js +0 -60
- package/dist/hooks/builtin/observability.d.ts +0 -20
- package/dist/hooks/builtin/observability.js +0 -572
- package/dist/hooks/helpers.d.ts +0 -22
- package/dist/hooks/helpers.js +0 -183
- package/dist/hooks/index.d.ts +0 -8
- package/dist/hooks/index.js +0 -6
- package/dist/realtime/RealtimeAudioClient.d.ts +0 -166
- package/dist/realtime/RealtimeAudioClient.js +0 -14
- package/dist/realtime/RealtimeSessionHandle.d.ts +0 -6
- package/dist/realtime/RealtimeSessionHandle.js +0 -1
- package/dist/realtime/index.d.ts +0 -2
- package/dist/realtime/index.js +0 -1
- package/dist/runtime/channels/VoiceDriver.d.ts +0 -31
- package/dist/runtime/channels/VoiceDriver.js +0 -321
- package/dist/runtime/channels/voiceTools.d.ts +0 -9
- package/dist/runtime/channels/voiceTools.js +0 -51
- package/dist/tools/errorHandling.d.ts +0 -35
- package/dist/tools/errorHandling.js +0 -145
- package/dist/types/voice.d.ts +0 -517
- package/dist/types/voice.js +0 -14
- package/dist/utils/isRecord.d.ts +0 -1
- package/dist/utils/isRecord.js +0 -3
- package/dist/utils/messageNormalization.d.ts +0 -2
- package/dist/utils/messageNormalization.js +0 -137
- /package/dist/{foundation/AgentDefinition.js → types/knowledge.js} +0 -0
- /package/dist/{foundation/ConversationEventLog.js → types/trace.js} +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
export class TraceRecorder {
|
|
2
|
+
trace;
|
|
3
|
+
root;
|
|
4
|
+
currentFlow;
|
|
5
|
+
currentNode;
|
|
6
|
+
openTools = [];
|
|
7
|
+
toolCallIds = new Map();
|
|
8
|
+
emitted = new Set();
|
|
9
|
+
onSpan;
|
|
10
|
+
currentAgentId;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
const startedAt = Date.now();
|
|
13
|
+
const sessionId = options.sessionId ?? '';
|
|
14
|
+
const traceId = crypto.randomUUID().replaceAll('-', '');
|
|
15
|
+
this.onSpan = options.onSpan;
|
|
16
|
+
this.currentAgentId = options.agentId;
|
|
17
|
+
this.root = {
|
|
18
|
+
traceId,
|
|
19
|
+
spanId: newSpanId(),
|
|
20
|
+
name: 'turn',
|
|
21
|
+
kind: 'turn',
|
|
22
|
+
startTime: startedAt,
|
|
23
|
+
status: 'ok',
|
|
24
|
+
attributes: {
|
|
25
|
+
sessionId,
|
|
26
|
+
...(options.agentId ? { agentId: options.agentId } : {}),
|
|
27
|
+
...(options.input !== undefined ? { input: toJsonValue(options.input) } : {}),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
this.trace = {
|
|
31
|
+
traceId,
|
|
32
|
+
sessionId,
|
|
33
|
+
spans: [this.root],
|
|
34
|
+
answer: '',
|
|
35
|
+
usedTool: false,
|
|
36
|
+
toolCalls: [],
|
|
37
|
+
toolResults: [],
|
|
38
|
+
startedAt,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
setInitiatingAgent(agentId) {
|
|
42
|
+
this.currentAgentId = agentId;
|
|
43
|
+
this.root.attributes.agentId = agentId;
|
|
44
|
+
}
|
|
45
|
+
record(part) {
|
|
46
|
+
try {
|
|
47
|
+
const at = Date.now();
|
|
48
|
+
switch (part.type) {
|
|
49
|
+
case 'text-delta':
|
|
50
|
+
this.trace.answer += part.payload.delta;
|
|
51
|
+
break;
|
|
52
|
+
case 'flow-enter':
|
|
53
|
+
this.closeNode(at);
|
|
54
|
+
this.closeFlow(at);
|
|
55
|
+
this.currentFlow = this.openSpan({
|
|
56
|
+
name: `flow:${part.payload.flow}`,
|
|
57
|
+
kind: 'flow',
|
|
58
|
+
parentSpanId: this.root.spanId,
|
|
59
|
+
at,
|
|
60
|
+
attributes: { activeFlow: part.payload.flow },
|
|
61
|
+
});
|
|
62
|
+
break;
|
|
63
|
+
case 'flow-end':
|
|
64
|
+
this.closeNode(at);
|
|
65
|
+
this.closeFlow(at);
|
|
66
|
+
break;
|
|
67
|
+
case 'node-enter':
|
|
68
|
+
this.closeNode(at);
|
|
69
|
+
this.currentNode = this.openSpan({
|
|
70
|
+
name: `node:${part.payload.nodeName}`,
|
|
71
|
+
kind: 'node',
|
|
72
|
+
parentSpanId: this.currentFlow?.spanId ?? this.root.spanId,
|
|
73
|
+
at,
|
|
74
|
+
attributes: {
|
|
75
|
+
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
76
|
+
nodeId: part.payload.nodeName,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
case 'node-exit':
|
|
81
|
+
this.closeNode(at);
|
|
82
|
+
break;
|
|
83
|
+
case 'tool-call': {
|
|
84
|
+
const span = this.openSpan({
|
|
85
|
+
name: `tool:${part.payload.toolName}`,
|
|
86
|
+
kind: 'tool',
|
|
87
|
+
parentSpanId: this.currentNode?.spanId ?? this.root.spanId,
|
|
88
|
+
at,
|
|
89
|
+
attributes: {
|
|
90
|
+
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
91
|
+
...(this.currentNode ? { nodeId: nodeName(this.currentNode) } : {}),
|
|
92
|
+
toolName: part.payload.toolName,
|
|
93
|
+
input: toJsonValue(part.payload.args),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
this.toolCallIds.set(span, part.payload.toolCallId);
|
|
97
|
+
this.openTools.push(span);
|
|
98
|
+
this.trace.usedTool = true;
|
|
99
|
+
this.trace.toolCalls.push({
|
|
100
|
+
name: part.payload.toolName,
|
|
101
|
+
args: toJsonValue(part.payload.args),
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case 'tool-result': {
|
|
106
|
+
this.trace.usedTool = true;
|
|
107
|
+
const result = toJsonValue(part.payload.result);
|
|
108
|
+
this.trace.toolResults.push({ name: part.payload.toolName, result });
|
|
109
|
+
const span = this.takeToolSpan(part.payload.toolName, part.payload.toolCallId) ??
|
|
110
|
+
this.openSpan({
|
|
111
|
+
name: `tool:${part.payload.toolName}`,
|
|
112
|
+
kind: 'tool',
|
|
113
|
+
parentSpanId: this.currentNode?.spanId ?? this.root.spanId,
|
|
114
|
+
at,
|
|
115
|
+
attributes: {
|
|
116
|
+
...(this.currentFlow ? { activeFlow: activeFlowName(this.currentFlow) } : {}),
|
|
117
|
+
...(this.currentNode ? { nodeId: nodeName(this.currentNode) } : {}),
|
|
118
|
+
toolName: part.payload.toolName,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
span.attributes.output = result;
|
|
122
|
+
span.endTime = at;
|
|
123
|
+
this.emitSpan(span);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'handoff': {
|
|
127
|
+
const handoffFrom = this.currentAgentId;
|
|
128
|
+
const span = this.openSpan({
|
|
129
|
+
name: `handoff:${part.payload.targetAgent}`,
|
|
130
|
+
kind: 'handoff',
|
|
131
|
+
parentSpanId: this.root.spanId,
|
|
132
|
+
at,
|
|
133
|
+
attributes: {
|
|
134
|
+
...(handoffFrom ? { handoffFrom } : {}),
|
|
135
|
+
handoffTo: part.payload.targetAgent,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
span.endTime = at;
|
|
139
|
+
this.emitSpan(span);
|
|
140
|
+
this.currentAgentId = part.payload.targetAgent;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'error': {
|
|
144
|
+
const span = this.openTools.at(-1) ?? this.currentNode ?? this.currentFlow ?? this.root;
|
|
145
|
+
span.status = 'error';
|
|
146
|
+
span.attributes.error = part.payload.error;
|
|
147
|
+
this.root.status = 'error';
|
|
148
|
+
this.root.attributes.error = part.payload.error;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'done':
|
|
152
|
+
this.setSessionId(part.payload.sessionId);
|
|
153
|
+
this.root.attributes.output = this.trace.answer;
|
|
154
|
+
if (part.payload.usage) {
|
|
155
|
+
if (typeof part.payload.usage.inputTokens === 'number') {
|
|
156
|
+
this.root.attributes.tokensIn = part.payload.usage.inputTokens;
|
|
157
|
+
}
|
|
158
|
+
if (typeof part.payload.usage.outputTokens === 'number') {
|
|
159
|
+
this.root.attributes.tokensOut = part.payload.usage.outputTokens;
|
|
160
|
+
}
|
|
161
|
+
if (typeof part.payload.usage.contextTokens === 'number') {
|
|
162
|
+
this.root.attributes.contextTokens = part.payload.usage.contextTokens;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.close(at);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Tracing is observational: malformed telemetry must never affect the run.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
finish(result) {
|
|
174
|
+
try {
|
|
175
|
+
if (!this.trace.answer) {
|
|
176
|
+
this.trace.answer = result.text;
|
|
177
|
+
}
|
|
178
|
+
this.root.attributes.output = this.trace.answer;
|
|
179
|
+
this.close(Date.now());
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Returning a partial trace is preferable to changing run behavior.
|
|
183
|
+
}
|
|
184
|
+
return this.trace;
|
|
185
|
+
}
|
|
186
|
+
openSpan(args) {
|
|
187
|
+
const span = {
|
|
188
|
+
traceId: this.trace.traceId,
|
|
189
|
+
spanId: newSpanId(),
|
|
190
|
+
parentSpanId: args.parentSpanId,
|
|
191
|
+
name: args.name,
|
|
192
|
+
kind: args.kind,
|
|
193
|
+
startTime: args.at,
|
|
194
|
+
status: 'ok',
|
|
195
|
+
attributes: {
|
|
196
|
+
sessionId: this.trace.sessionId,
|
|
197
|
+
...(this.currentAgentId ? { agentId: this.currentAgentId } : {}),
|
|
198
|
+
...args.attributes,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
this.trace.spans.push(span);
|
|
202
|
+
return span;
|
|
203
|
+
}
|
|
204
|
+
takeToolSpan(toolName, toolCallId) {
|
|
205
|
+
const index = this.openTools.findIndex((span) => {
|
|
206
|
+
const spanCallId = this.toolCallIds.get(span);
|
|
207
|
+
return toolCallId ? spanCallId === toolCallId : span.attributes.toolName === toolName;
|
|
208
|
+
});
|
|
209
|
+
if (index < 0)
|
|
210
|
+
return undefined;
|
|
211
|
+
const span = this.openTools.splice(index, 1)[0];
|
|
212
|
+
if (span)
|
|
213
|
+
this.toolCallIds.delete(span);
|
|
214
|
+
return span;
|
|
215
|
+
}
|
|
216
|
+
closeTools(at) {
|
|
217
|
+
for (const span of this.openTools.splice(0)) {
|
|
218
|
+
span.endTime ??= at;
|
|
219
|
+
this.toolCallIds.delete(span);
|
|
220
|
+
this.emitSpan(span);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
closeNode(at) {
|
|
224
|
+
this.closeTools(at);
|
|
225
|
+
if (this.currentNode) {
|
|
226
|
+
this.currentNode.endTime ??= at;
|
|
227
|
+
this.emitSpan(this.currentNode);
|
|
228
|
+
this.currentNode = undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
closeFlow(at) {
|
|
232
|
+
if (this.currentFlow) {
|
|
233
|
+
this.currentFlow.endTime ??= at;
|
|
234
|
+
this.emitSpan(this.currentFlow);
|
|
235
|
+
this.currentFlow = undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
setSessionId(sessionId) {
|
|
239
|
+
this.trace.sessionId = sessionId;
|
|
240
|
+
for (const span of this.trace.spans) {
|
|
241
|
+
span.attributes.sessionId = sessionId;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
close(at) {
|
|
245
|
+
this.closeNode(at);
|
|
246
|
+
this.closeFlow(at);
|
|
247
|
+
this.trace.endedAt ??= at;
|
|
248
|
+
this.root.endTime ??= at;
|
|
249
|
+
this.emitSpan(this.root);
|
|
250
|
+
}
|
|
251
|
+
emitSpan(span) {
|
|
252
|
+
if (this.emitted.has(span.spanId) || span.endTime === undefined)
|
|
253
|
+
return;
|
|
254
|
+
this.emitted.add(span.spanId);
|
|
255
|
+
try {
|
|
256
|
+
this.onSpan?.(structuredClone(span));
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// A tracing callback is never allowed to change run behavior.
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
export async function runOnce(runtime, opts) {
|
|
264
|
+
// Resolve attribution the same way Runtime.runOnce does: caller's agentId
|
|
265
|
+
// first, then persisted state, then the runtime default. Without this the
|
|
266
|
+
// standalone helper produced spans with no `agentId` and handoff spans with
|
|
267
|
+
// no `handoffFrom` — the exact feature it is documented to provide.
|
|
268
|
+
let agentId = opts.agentId;
|
|
269
|
+
if (!agentId && opts.sessionId && runtime.getSessionStore) {
|
|
270
|
+
const existing = await runtime.getSessionStore().get(opts.sessionId);
|
|
271
|
+
agentId = existing?.activeAgentId ?? existing?.currentAgent;
|
|
272
|
+
}
|
|
273
|
+
agentId ??= runtime.getDefaultAgentId?.();
|
|
274
|
+
const handle = runtime.run(opts);
|
|
275
|
+
const recorder = new TraceRecorder({
|
|
276
|
+
sessionId: opts.sessionId,
|
|
277
|
+
agentId,
|
|
278
|
+
input: opts.input,
|
|
279
|
+
});
|
|
280
|
+
for await (const part of handle.events) {
|
|
281
|
+
recorder.record(part);
|
|
282
|
+
}
|
|
283
|
+
return recorder.finish(await handle);
|
|
284
|
+
}
|
|
285
|
+
function activeFlowName(span) {
|
|
286
|
+
return span.attributes.activeFlow ?? span.name.slice('flow:'.length);
|
|
287
|
+
}
|
|
288
|
+
function nodeName(span) {
|
|
289
|
+
return span.attributes.nodeId ?? span.name.slice('node:'.length);
|
|
290
|
+
}
|
|
291
|
+
function newSpanId() {
|
|
292
|
+
return crypto.randomUUID().replaceAll('-', '').slice(0, 16);
|
|
293
|
+
}
|
|
294
|
+
function toJsonValue(value, seen = new WeakSet()) {
|
|
295
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
296
|
+
return value;
|
|
297
|
+
if (typeof value === 'number')
|
|
298
|
+
return Number.isFinite(value) ? value : String(value);
|
|
299
|
+
if (typeof value === 'bigint')
|
|
300
|
+
return value.toString();
|
|
301
|
+
if (value === undefined)
|
|
302
|
+
return null;
|
|
303
|
+
if (typeof value === 'function' || typeof value === 'symbol')
|
|
304
|
+
return String(value);
|
|
305
|
+
if (typeof value !== 'object')
|
|
306
|
+
return String(value);
|
|
307
|
+
if (seen.has(value))
|
|
308
|
+
return '[Circular]';
|
|
309
|
+
seen.add(value);
|
|
310
|
+
if (value instanceof Date)
|
|
311
|
+
return value.toISOString();
|
|
312
|
+
if (value instanceof Error)
|
|
313
|
+
return { name: value.name, message: value.message };
|
|
314
|
+
if (Array.isArray(value))
|
|
315
|
+
return value.map((entry) => toJsonValue(entry, seen));
|
|
316
|
+
const output = {};
|
|
317
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
318
|
+
try {
|
|
319
|
+
output[key] = toJsonValue(entry, seen);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
output[key] = '[Unserializable]';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return output;
|
|
326
|
+
}
|
|
@@ -28,7 +28,7 @@ export async function buildAgentToolSurface(agent, session, deps) {
|
|
|
28
28
|
let skillPrompt;
|
|
29
29
|
let skillTools = {};
|
|
30
30
|
if (agent.skills) {
|
|
31
|
-
const wired = await wireAgentSkills(agent);
|
|
31
|
+
const wired = await wireAgentSkills(agent, resolvedWorkspace?.fs);
|
|
32
32
|
if (wired) {
|
|
33
33
|
skillTools = wired.tools;
|
|
34
34
|
Object.assign(executorTools, wired.tools);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { streamText } from 'ai';
|
|
2
2
|
import { buildNodePrompt, resolveInstructions, composeSystem } from '../../flow/nodeBuilders.js';
|
|
3
3
|
import { buildToolSet } from '../../tools/effect/index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { dispatchModelToolCalls, toolResultMessage } from './executeModelTool.js';
|
|
5
5
|
import { consumeAllPendingUserInput } from './inputBuffer.js';
|
|
6
6
|
import { runSilentExtraction } from './extractionTurn.js';
|
|
7
7
|
import { applyPreTurnPolicies, applyPostTurnPolicies } from '../policies/agentTurn.js';
|
|
@@ -29,21 +29,25 @@ export class TextDriver {
|
|
|
29
29
|
if (!preTurn.proceed) {
|
|
30
30
|
const blocked = preTurn.blockedMessage ?? 'Input blocked by guardrails';
|
|
31
31
|
ctx.emit({
|
|
32
|
+
channel: 'internal',
|
|
32
33
|
type: 'safety-blocked',
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
payload: {
|
|
35
|
+
moderator: preTurn.blockedBy ?? 'input-guardrails',
|
|
36
|
+
rationale: preTurn.blockedReason ?? 'input blocked',
|
|
37
|
+
userFacingMessage: blocked,
|
|
38
|
+
},
|
|
36
39
|
});
|
|
37
40
|
const id = crypto.randomUUID();
|
|
38
|
-
ctx.emit({ type: 'text-start', id });
|
|
39
|
-
ctx.emit({ type: 'text-delta', id, delta: blocked });
|
|
40
|
-
ctx.emit({ type: 'text-end', id });
|
|
41
|
-
ctx.emit({ type: 'turn-end' });
|
|
41
|
+
ctx.emit({ channel: 'client', type: 'text-start', payload: { id } });
|
|
42
|
+
ctx.emit({ channel: 'client', type: 'text-delta', payload: { id, delta: blocked } });
|
|
43
|
+
ctx.emit({ channel: 'client', type: 'text-end', payload: { id } });
|
|
44
|
+
ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
|
|
42
45
|
return { text: blocked, toolResults: [] };
|
|
43
46
|
}
|
|
44
47
|
const scope = resolveNodeGatherScope(replyNode, ctx.runState.state, ctx.runState.messages);
|
|
45
48
|
const gather = await runGatherPhase(ctx, scope);
|
|
46
49
|
const out = { text: '', toolResults: [] };
|
|
50
|
+
const toolMessages = [];
|
|
47
51
|
const model = replyNode.model ?? ctx.model;
|
|
48
52
|
const nodeSystem = node.prompt || buildNodePrompt(replyNode, ctx.runState.state);
|
|
49
53
|
const baseSystem = composeSystem(ctx.baseInstructions, nodeSystem, ctx.runState.state, ctx.skillPrompt, ctx.workingMemoryPrompt);
|
|
@@ -54,6 +58,7 @@ export class TextDriver {
|
|
|
54
58
|
const toolCallsMade = [];
|
|
55
59
|
const mode = resolveStreamMode(ctx, node);
|
|
56
60
|
const turnId = crypto.randomUUID();
|
|
61
|
+
let turnUsage;
|
|
57
62
|
const source = {
|
|
58
63
|
async *[Symbol.asyncIterator]() {
|
|
59
64
|
for (let step = 0; step < maxSteps; step += 1) {
|
|
@@ -73,29 +78,32 @@ export class TextDriver {
|
|
|
73
78
|
if (part.type === 'error') {
|
|
74
79
|
const err = part.error;
|
|
75
80
|
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
-
ctx.emit({ type: 'error', error: message });
|
|
81
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
77
82
|
throw err instanceof Error ? err : new Error(message);
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
const finishReason = await result.finishReason;
|
|
81
86
|
const response = await result.response;
|
|
82
|
-
|
|
87
|
+
if (result.totalUsage) {
|
|
88
|
+
const stepUsage = await result.totalUsage;
|
|
89
|
+
if (stepUsage) {
|
|
90
|
+
turnUsage = addTurnUsage(turnUsage, stepUsage);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
83
93
|
if (finishReason !== 'tool-calls') {
|
|
94
|
+
messages.push(...response.messages);
|
|
84
95
|
break;
|
|
85
96
|
}
|
|
97
|
+
messages.push(...response.messages);
|
|
98
|
+
toolMessages.push(...response.messages);
|
|
86
99
|
const toolCalls = await result.toolCalls;
|
|
87
|
-
|
|
88
|
-
ctx.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
const { result: toolResult, control, failed } = await executeModelToolCall(ctx, { toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, {
|
|
95
|
-
...ctx.globalTools,
|
|
96
|
-
...(ctx.workingMemoryTools ?? {}),
|
|
97
|
-
...node.localTools,
|
|
98
|
-
});
|
|
100
|
+
const mergedTools = {
|
|
101
|
+
...ctx.globalTools,
|
|
102
|
+
...(ctx.workingMemoryTools ?? {}),
|
|
103
|
+
...node.localTools,
|
|
104
|
+
};
|
|
105
|
+
await dispatchModelToolCalls(ctx, toolCalls, mergedTools, ({ call, outcome }) => {
|
|
106
|
+
const { result: toolResult, control, failed } = outcome;
|
|
99
107
|
out.toolResults.push({
|
|
100
108
|
name: call.toolName,
|
|
101
109
|
args: call.input,
|
|
@@ -111,14 +119,10 @@ export class TextDriver {
|
|
|
111
119
|
timestamp: Date.now(),
|
|
112
120
|
});
|
|
113
121
|
out.control ??= control;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
toolCallId: call.toolCallId,
|
|
119
|
-
});
|
|
120
|
-
messages.push(toolResultMessage({ toolName: call.toolName, input: call.input, toolCallId: call.toolCallId }, toolResult));
|
|
121
|
-
}
|
|
122
|
+
const resultMessage = toolResultMessage(call, toolResult);
|
|
123
|
+
messages.push(resultMessage);
|
|
124
|
+
toolMessages.push(resultMessage);
|
|
125
|
+
});
|
|
122
126
|
}
|
|
123
127
|
},
|
|
124
128
|
};
|
|
@@ -153,7 +157,13 @@ export class TextDriver {
|
|
|
153
157
|
out.text = spoken.text;
|
|
154
158
|
out.control = spoken.control ?? out.control;
|
|
155
159
|
out.confidence = spoken.confidence;
|
|
156
|
-
|
|
160
|
+
if (toolMessages.length > 0) {
|
|
161
|
+
out.toolMessages = toolMessages;
|
|
162
|
+
}
|
|
163
|
+
if (turnUsage && turnUsage.totalTokens > 0) {
|
|
164
|
+
out.usage = turnUsage;
|
|
165
|
+
}
|
|
166
|
+
ctx.emit({ channel: 'internal', type: 'turn-end', payload: {} });
|
|
157
167
|
return out;
|
|
158
168
|
}
|
|
159
169
|
// Non-speaking field extraction for collect nodes (shared helper so text and
|
|
@@ -210,4 +220,20 @@ export class TextDriver {
|
|
|
210
220
|
return aiTools;
|
|
211
221
|
}
|
|
212
222
|
}
|
|
223
|
+
function addTurnUsage(current, usage) {
|
|
224
|
+
const inputTokens = usage.inputTokens ?? 0;
|
|
225
|
+
const outputTokens = usage.outputTokens ?? 0;
|
|
226
|
+
const totalTokens = usage.totalTokens ?? inputTokens + outputTokens;
|
|
227
|
+
const cacheReadTokens = usage.inputTokenDetails?.cacheReadTokens ?? 0;
|
|
228
|
+
if (!current) {
|
|
229
|
+
return { inputTokens, outputTokens, totalTokens, cacheReadTokens, contextTokens: inputTokens };
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
inputTokens: current.inputTokens + inputTokens,
|
|
233
|
+
outputTokens: current.outputTokens + outputTokens,
|
|
234
|
+
totalTokens: current.totalTokens + totalTokens,
|
|
235
|
+
cacheReadTokens: (current.cacheReadTokens ?? 0) + cacheReadTokens,
|
|
236
|
+
contextTokens: inputTokens,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
213
239
|
export { buildNodePrompt };
|
|
@@ -11,8 +11,21 @@ export interface ModelToolCallOutcome {
|
|
|
11
11
|
result: unknown;
|
|
12
12
|
control?: TurnControl;
|
|
13
13
|
failed: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Set when the call unwound with a control-flow signal rather than a failure. Held as a
|
|
16
|
+
* value so a parallel batch can still finalize its siblings; the dispatcher rethrows it
|
|
17
|
+
* once the batch has settled. Never surfaced to the model.
|
|
18
|
+
*/
|
|
19
|
+
signal?: unknown;
|
|
14
20
|
}
|
|
15
|
-
export declare function executeModelToolCall(ctx: RunContext, call: ModelToolCall, localTools?: Record<string, AnyTool
|
|
21
|
+
export declare function executeModelToolCall(ctx: RunContext, call: ModelToolCall, localTools?: Record<string, AnyTool>, durableOpts?: {
|
|
22
|
+
callsite?: string;
|
|
23
|
+
index?: number;
|
|
24
|
+
}): Promise<ModelToolCallOutcome>;
|
|
25
|
+
export declare function dispatchModelToolCalls(ctx: RunContext, toolCalls: ModelToolCall[], localTools: Record<string, AnyTool>, onEach: (args: {
|
|
26
|
+
call: ModelToolCall;
|
|
27
|
+
outcome: ModelToolCallOutcome;
|
|
28
|
+
}) => void): Promise<void>;
|
|
16
29
|
export declare function toolResultMessage(call: ModelToolCall, result: unknown): {
|
|
17
30
|
role: 'tool';
|
|
18
31
|
content: [
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { classifyControl } from '../../flow/classifyControl.js';
|
|
2
|
-
import { toolErrorResult } from '../../tools/controlResults.js';
|
|
3
|
-
|
|
2
|
+
import { toolDeniedResult, toolErrorResult } from '../../tools/controlResults.js';
|
|
3
|
+
import { idempotencyKey, logicalRunId } from '../durable/idempotency.js';
|
|
4
|
+
import { findStepByKey } from '../durable/replay.js';
|
|
5
|
+
import { isApprovalDenial, isControlFlowSignal } from '../controlFlowSignal.js';
|
|
6
|
+
export async function executeModelToolCall(ctx, call, localTools, durableOpts) {
|
|
4
7
|
try {
|
|
5
8
|
const localTool = localTools?.[call.toolName];
|
|
6
9
|
const toolResult = await ctx.tool(call.toolName, call.input, {
|
|
7
10
|
toolCallId: call.toolCallId,
|
|
11
|
+
callsite: durableOpts?.callsite,
|
|
12
|
+
index: durableOpts?.index,
|
|
8
13
|
...(localTool && {
|
|
9
14
|
def: localTool,
|
|
10
15
|
toolCtx: {
|
|
@@ -20,11 +25,135 @@ export async function executeModelToolCall(ctx, call, localTools) {
|
|
|
20
25
|
return { result: toolResult, control: classifyControl(toolResult), failed: false };
|
|
21
26
|
}
|
|
22
27
|
catch (error) {
|
|
28
|
+
if (isControlFlowSignal(error)) {
|
|
29
|
+
// A suspend. Not a failure, so no client-facing error and no tool result: returning it
|
|
30
|
+
// as a value keeps this function's never-rejects contract (which `Promise.all` below
|
|
31
|
+
// depends on) while letting the dispatcher rethrow it once the batch has settled.
|
|
32
|
+
return { result: undefined, failed: true, signal: error };
|
|
33
|
+
}
|
|
34
|
+
if (isApprovalDenial(error)) {
|
|
35
|
+
// A human declined. The model asked for this call, so the model is told — otherwise
|
|
36
|
+
// the turn dies and the user never hears why. No client error part: nothing broke.
|
|
37
|
+
return { result: toolDeniedResult(error.toolName, error.by), failed: true };
|
|
38
|
+
}
|
|
23
39
|
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
-
ctx.emit({ type: 'error', error: message });
|
|
40
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
25
41
|
return { result: toolErrorResult(error), failed: true };
|
|
26
42
|
}
|
|
27
43
|
}
|
|
44
|
+
function isParallelSafeTool(def) {
|
|
45
|
+
return def?.parallelSafe === true || def?.replay === false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Runs `task` over every item, at most `limit` at a time, preserving result order.
|
|
49
|
+
*
|
|
50
|
+
* Unbounded `Promise.all` over a model-emitted batch lets the model decide how many
|
|
51
|
+
* sockets, subprocesses, or rate-limited API calls open at once. `limit` is a ceiling on
|
|
52
|
+
* that; omitting it keeps the previous unbounded behaviour.
|
|
53
|
+
*
|
|
54
|
+
* `task` is expected not to reject (see the caller's contract). If one does, the rejection
|
|
55
|
+
* propagates and in-flight siblings still run to completion — they are not cancelled, so
|
|
56
|
+
* their journal writes complete rather than being abandoned half-done.
|
|
57
|
+
*/
|
|
58
|
+
async function runWithConcurrency(items, task, limit) {
|
|
59
|
+
if (limit === undefined || limit <= 0 || limit >= items.length) {
|
|
60
|
+
return Promise.all(items.map((item) => task(item)));
|
|
61
|
+
}
|
|
62
|
+
const results = new Array(items.length);
|
|
63
|
+
let next = 0;
|
|
64
|
+
const worker = async () => {
|
|
65
|
+
while (next < items.length) {
|
|
66
|
+
const index = next++;
|
|
67
|
+
results[index] = await task(items[index]);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
await Promise.all(Array.from({ length: limit }, () => worker()));
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
export async function dispatchModelToolCalls(ctx, toolCalls, localTools, onEach) {
|
|
74
|
+
/** Resolves to a control-flow signal when the call suspended, otherwise `undefined`. */
|
|
75
|
+
const runOne = async (call, durableOpts) => {
|
|
76
|
+
ctx.emit({
|
|
77
|
+
channel: 'internal',
|
|
78
|
+
type: 'tool-call',
|
|
79
|
+
payload: {
|
|
80
|
+
toolName: call.toolName,
|
|
81
|
+
args: call.input,
|
|
82
|
+
toolCallId: call.toolCallId,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const outcome = await executeModelToolCall(ctx, call, localTools, durableOpts);
|
|
86
|
+
if (outcome.signal !== undefined) {
|
|
87
|
+
// Suspended: no result exists, so nothing may reach the model or the transcript.
|
|
88
|
+
return outcome.signal;
|
|
89
|
+
}
|
|
90
|
+
onEach({ call, outcome });
|
|
91
|
+
ctx.emit({
|
|
92
|
+
channel: 'internal',
|
|
93
|
+
type: 'tool-result',
|
|
94
|
+
payload: {
|
|
95
|
+
toolName: call.toolName,
|
|
96
|
+
result: outcome.result,
|
|
97
|
+
toolCallId: call.toolCallId,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
const runParallel = async (parallel) => {
|
|
103
|
+
const callsites = ctx.reserveCallsites(parallel.length);
|
|
104
|
+
const steps = await ctx.runStore.getSteps(ctx.runState.runId);
|
|
105
|
+
const logicalId = logicalRunId(ctx.runState.runId, ctx.runState.runEpoch);
|
|
106
|
+
const unresolved = parallel.filter((call, i) => {
|
|
107
|
+
const def = localTools[call.toolName];
|
|
108
|
+
const key = def?.idempotencyKey != null
|
|
109
|
+
? def.idempotencyKey(call.input)
|
|
110
|
+
: idempotencyKey(logicalId, callsites[i], { name: call.toolName, args: call.input });
|
|
111
|
+
const hit = findStepByKey(steps, key);
|
|
112
|
+
return !hit || hit.status === 'running';
|
|
113
|
+
});
|
|
114
|
+
let indices;
|
|
115
|
+
if (unresolved.length > 0 && ctx.runStore.reserveSteps) {
|
|
116
|
+
indices = await ctx.runStore.reserveSteps(ctx.runState.runId, unresolved.length);
|
|
117
|
+
}
|
|
118
|
+
let unresolvedCursor = 0;
|
|
119
|
+
const assignments = parallel.map((call, i) => {
|
|
120
|
+
const def = localTools[call.toolName];
|
|
121
|
+
const key = def?.idempotencyKey != null
|
|
122
|
+
? def.idempotencyKey(call.input)
|
|
123
|
+
: idempotencyKey(logicalId, callsites[i], { name: call.toolName, args: call.input });
|
|
124
|
+
const hit = findStepByKey(steps, key);
|
|
125
|
+
const needsIndex = !hit || hit.status === 'running';
|
|
126
|
+
const index = needsIndex ? indices?.[unresolvedCursor++] : hit?.index;
|
|
127
|
+
return { call, callsite: callsites[i], index };
|
|
128
|
+
});
|
|
129
|
+
// executeModelToolCall never rejects — tool errors resolve with failed: true and control-flow
|
|
130
|
+
// signals resolve as outcome.signal — so no sibling can be abandoned mid-flight with its
|
|
131
|
+
// finalizeStep unawaited. A suspend is rethrown only after every sibling has settled: you
|
|
132
|
+
// cannot cancel an in-flight promise, so failing fast here would let the work happen anyway
|
|
133
|
+
// while the journal recorded it as still running.
|
|
134
|
+
const signals = await runWithConcurrency(assignments, ({ call, callsite, index }) => runOne(call, { callsite, index }), ctx.limits?.maxToolConcurrency);
|
|
135
|
+
const suspended = signals.find((signal) => signal !== undefined);
|
|
136
|
+
if (suspended !== undefined)
|
|
137
|
+
throw suspended;
|
|
138
|
+
};
|
|
139
|
+
for (let cursor = 0; cursor < toolCalls.length;) {
|
|
140
|
+
const call = toolCalls[cursor];
|
|
141
|
+
if (!isParallelSafeTool(localTools[call.toolName])) {
|
|
142
|
+
const signal = await runOne(call);
|
|
143
|
+
if (signal !== undefined)
|
|
144
|
+
throw signal;
|
|
145
|
+
cursor += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const parallel = [];
|
|
149
|
+
while (cursor < toolCalls.length &&
|
|
150
|
+
isParallelSafeTool(localTools[toolCalls[cursor].toolName])) {
|
|
151
|
+
parallel.push(toolCalls[cursor]);
|
|
152
|
+
cursor += 1;
|
|
153
|
+
}
|
|
154
|
+
await runParallel(parallel);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
28
157
|
export function toolResultMessage(call, result) {
|
|
29
158
|
return {
|
|
30
159
|
role: 'tool',
|
|
@@ -36,7 +36,7 @@ export async function runSilentExtraction(node, ctx, model, maxSteps) {
|
|
|
36
36
|
if (part.type === 'error') {
|
|
37
37
|
const err = part.error;
|
|
38
38
|
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
-
ctx.emit({ type: 'error', error: message });
|
|
39
|
+
ctx.emit({ channel: 'client', type: 'error', payload: { error: message } });
|
|
40
40
|
throw err instanceof Error ? err : new Error(message);
|
|
41
41
|
}
|
|
42
42
|
}
|