@dogpile/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/LICENSE +16 -0
- package/README.md +842 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +4493 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +44 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +305 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/runtime/broadcast.d.ts +18 -0
- package/dist/runtime/broadcast.d.ts.map +1 -0
- package/dist/runtime/broadcast.js +335 -0
- package/dist/runtime/broadcast.js.map +1 -0
- package/dist/runtime/cancellation.d.ts +6 -0
- package/dist/runtime/cancellation.d.ts.map +1 -0
- package/dist/runtime/cancellation.js +35 -0
- package/dist/runtime/cancellation.js.map +1 -0
- package/dist/runtime/coordinator.d.ts +18 -0
- package/dist/runtime/coordinator.d.ts.map +1 -0
- package/dist/runtime/coordinator.js +434 -0
- package/dist/runtime/coordinator.js.map +1 -0
- package/dist/runtime/decisions.d.ts +5 -0
- package/dist/runtime/decisions.d.ts.map +1 -0
- package/dist/runtime/decisions.js +31 -0
- package/dist/runtime/decisions.js.map +1 -0
- package/dist/runtime/defaults.d.ts +63 -0
- package/dist/runtime/defaults.d.ts.map +1 -0
- package/dist/runtime/defaults.js +426 -0
- package/dist/runtime/defaults.js.map +1 -0
- package/dist/runtime/engine.d.ts +79 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +723 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/model.d.ts +14 -0
- package/dist/runtime/model.d.ts.map +1 -0
- package/dist/runtime/model.js +82 -0
- package/dist/runtime/model.js.map +1 -0
- package/dist/runtime/sequential.d.ts +18 -0
- package/dist/runtime/sequential.d.ts.map +1 -0
- package/dist/runtime/sequential.js +277 -0
- package/dist/runtime/sequential.js.map +1 -0
- package/dist/runtime/shared.d.ts +18 -0
- package/dist/runtime/shared.d.ts.map +1 -0
- package/dist/runtime/shared.js +288 -0
- package/dist/runtime/shared.js.map +1 -0
- package/dist/runtime/termination.d.ts +77 -0
- package/dist/runtime/termination.d.ts.map +1 -0
- package/dist/runtime/termination.js +355 -0
- package/dist/runtime/termination.js.map +1 -0
- package/dist/runtime/tools.d.ts +314 -0
- package/dist/runtime/tools.d.ts.map +1 -0
- package/dist/runtime/tools.js +969 -0
- package/dist/runtime/tools.js.map +1 -0
- package/dist/runtime/validation.d.ts +23 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/runtime/validation.js +656 -0
- package/dist/runtime/validation.js.map +1 -0
- package/dist/types.d.ts +2434 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/package.json +157 -0
- package/src/browser/index.ts +7 -0
- package/src/index.ts +195 -0
- package/src/providers/openai-compatible.ts +406 -0
- package/src/runtime/broadcast.test.ts +355 -0
- package/src/runtime/broadcast.ts +428 -0
- package/src/runtime/cancellation.ts +40 -0
- package/src/runtime/coordinator.test.ts +468 -0
- package/src/runtime/coordinator.ts +581 -0
- package/src/runtime/decisions.ts +38 -0
- package/src/runtime/defaults.ts +547 -0
- package/src/runtime/engine.ts +880 -0
- package/src/runtime/model.ts +117 -0
- package/src/runtime/sequential.test.ts +262 -0
- package/src/runtime/sequential.ts +357 -0
- package/src/runtime/shared.test.ts +265 -0
- package/src/runtime/shared.ts +367 -0
- package/src/runtime/termination.ts +463 -0
- package/src/runtime/tools.ts +1518 -0
- package/src/runtime/validation.ts +771 -0
- package/src/types.ts +2729 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentSpec,
|
|
3
|
+
ConfiguredModelProvider,
|
|
4
|
+
CoordinatorProtocolConfig,
|
|
5
|
+
CostSummary,
|
|
6
|
+
DogpileOptions,
|
|
7
|
+
JsonObject,
|
|
8
|
+
JsonValue,
|
|
9
|
+
ModelRequest,
|
|
10
|
+
ModelResponse,
|
|
11
|
+
ReplayTraceProtocolDecision,
|
|
12
|
+
ReplayTraceProviderCall,
|
|
13
|
+
RuntimeTool,
|
|
14
|
+
RuntimeToolExecutor,
|
|
15
|
+
RunEvent,
|
|
16
|
+
RunResult,
|
|
17
|
+
TerminationCondition,
|
|
18
|
+
TerminationStopRecord,
|
|
19
|
+
Tier,
|
|
20
|
+
TranscriptEntry
|
|
21
|
+
} from "../types.js";
|
|
22
|
+
import {
|
|
23
|
+
addCost,
|
|
24
|
+
createReplayTraceBudget,
|
|
25
|
+
createReplayTraceBudgetStateChanges,
|
|
26
|
+
createReplayTraceFinalOutput,
|
|
27
|
+
createReplayTraceProtocolDecision,
|
|
28
|
+
createReplayTraceRunInputs,
|
|
29
|
+
createReplayTraceSeed,
|
|
30
|
+
createRunAccounting,
|
|
31
|
+
createRunEventLog,
|
|
32
|
+
createRunMetadata,
|
|
33
|
+
createRunUsage,
|
|
34
|
+
createTranscriptLink,
|
|
35
|
+
emptyCost,
|
|
36
|
+
nextProviderCallId
|
|
37
|
+
} from "./defaults.js";
|
|
38
|
+
import { throwIfAborted } from "./cancellation.js";
|
|
39
|
+
import { parseAgentDecision } from "./decisions.js";
|
|
40
|
+
import { generateModelTurn } from "./model.js";
|
|
41
|
+
import { evaluateTerminationStop } from "./termination.js";
|
|
42
|
+
import { createRuntimeToolExecutor, executeModelResponseToolRequests, runtimeToolAvailability } from "./tools.js";
|
|
43
|
+
|
|
44
|
+
interface CoordinatorRunOptions {
|
|
45
|
+
readonly intent: string;
|
|
46
|
+
readonly protocol: CoordinatorProtocolConfig;
|
|
47
|
+
readonly tier: Tier;
|
|
48
|
+
readonly model: ConfiguredModelProvider;
|
|
49
|
+
readonly agents: readonly AgentSpec[];
|
|
50
|
+
readonly tools: readonly RuntimeTool<JsonObject, JsonValue>[];
|
|
51
|
+
readonly temperature: number;
|
|
52
|
+
readonly budget?: DogpileOptions["budget"];
|
|
53
|
+
readonly seed?: string | number;
|
|
54
|
+
readonly signal?: AbortSignal;
|
|
55
|
+
readonly terminate?: TerminationCondition;
|
|
56
|
+
readonly emit?: (event: RunEvent) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function runCoordinator(options: CoordinatorRunOptions): Promise<RunResult> {
|
|
60
|
+
const runId = createRunId();
|
|
61
|
+
const events: RunEvent[] = [];
|
|
62
|
+
const transcript: TranscriptEntry[] = [];
|
|
63
|
+
const protocolDecisions: ReplayTraceProtocolDecision[] = [];
|
|
64
|
+
const providerCalls: ReplayTraceProviderCall[] = [];
|
|
65
|
+
let totalCost = emptyCost();
|
|
66
|
+
const maxTurns = options.protocol.maxTurns ?? options.agents.length;
|
|
67
|
+
const activeAgents = options.agents.slice(0, maxTurns);
|
|
68
|
+
const coordinator = activeAgents[0];
|
|
69
|
+
const startedAtMs = nowMs();
|
|
70
|
+
let stopped = false;
|
|
71
|
+
let termination: TerminationStopRecord | undefined;
|
|
72
|
+
|
|
73
|
+
const emit = (event: RunEvent): void => {
|
|
74
|
+
events.push(event);
|
|
75
|
+
options.emit?.(event);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const recordProtocolDecision = (
|
|
79
|
+
event: RunEvent,
|
|
80
|
+
decisionOptions?: Parameters<typeof createReplayTraceProtocolDecision>[3]
|
|
81
|
+
): void => {
|
|
82
|
+
protocolDecisions.push(
|
|
83
|
+
createReplayTraceProtocolDecision("coordinator", event, events.length - 1, decisionOptions)
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const toolExecutor = createRuntimeToolExecutor({
|
|
88
|
+
runId,
|
|
89
|
+
protocol: "coordinator",
|
|
90
|
+
tier: options.tier,
|
|
91
|
+
tools: options.tools,
|
|
92
|
+
emit(event): void {
|
|
93
|
+
emit(event);
|
|
94
|
+
recordProtocolDecision(event);
|
|
95
|
+
},
|
|
96
|
+
getTrace: () => ({ events, transcript }),
|
|
97
|
+
...(options.signal !== undefined ? { abortSignal: options.signal } : {})
|
|
98
|
+
});
|
|
99
|
+
const toolAvailability = runtimeToolAvailability(toolExecutor.tools);
|
|
100
|
+
|
|
101
|
+
throwIfAborted(options.signal, options.model.id);
|
|
102
|
+
|
|
103
|
+
for (const agent of activeAgents) {
|
|
104
|
+
const event: RunEvent = {
|
|
105
|
+
type: "role-assignment",
|
|
106
|
+
runId,
|
|
107
|
+
at: new Date().toISOString(),
|
|
108
|
+
agentId: agent.id,
|
|
109
|
+
role: agent.role
|
|
110
|
+
};
|
|
111
|
+
emit(event);
|
|
112
|
+
recordProtocolDecision(event);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (coordinator) {
|
|
116
|
+
if (!stopIfNeeded()) {
|
|
117
|
+
totalCost = await runCoordinatorTurn({
|
|
118
|
+
agent: coordinator,
|
|
119
|
+
coordinator,
|
|
120
|
+
input: buildCoordinatorPlanInput(options.intent, coordinator),
|
|
121
|
+
phase: "plan",
|
|
122
|
+
options,
|
|
123
|
+
runId,
|
|
124
|
+
transcript,
|
|
125
|
+
totalCost,
|
|
126
|
+
providerCalls,
|
|
127
|
+
toolExecutor,
|
|
128
|
+
toolAvailability,
|
|
129
|
+
emit,
|
|
130
|
+
recordProtocolDecision
|
|
131
|
+
});
|
|
132
|
+
stopIfNeeded();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!stopIfNeeded()) {
|
|
136
|
+
const workers = activeAgents.slice(1);
|
|
137
|
+
const providerCallSlots: ReplayTraceProviderCall[] = [];
|
|
138
|
+
const planTranscript = [...transcript];
|
|
139
|
+
const workerResults = await Promise.all(
|
|
140
|
+
workers.map((agent, index) =>
|
|
141
|
+
runCoordinatorWorkerTurn({
|
|
142
|
+
agent,
|
|
143
|
+
coordinator,
|
|
144
|
+
input: buildWorkerInput(options.intent, planTranscript, coordinator),
|
|
145
|
+
options,
|
|
146
|
+
runId,
|
|
147
|
+
turn: transcript.length + index + 1,
|
|
148
|
+
providerCallId: providerCallIdFor(runId, providerCalls.length + index + 1),
|
|
149
|
+
providerCallIndex: index,
|
|
150
|
+
providerCallSlots,
|
|
151
|
+
toolExecutor,
|
|
152
|
+
toolAvailability,
|
|
153
|
+
emit
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
providerCalls.push(...providerCallSlots.filter((call): call is ReplayTraceProviderCall => call !== undefined));
|
|
158
|
+
|
|
159
|
+
for (const result of workerResults) {
|
|
160
|
+
totalCost = addCost(totalCost, result.turnCost);
|
|
161
|
+
transcript.push({
|
|
162
|
+
agentId: result.agent.id,
|
|
163
|
+
role: result.agent.role,
|
|
164
|
+
input: result.input,
|
|
165
|
+
output: result.response.text,
|
|
166
|
+
...(result.decision !== undefined ? { decision: result.decision } : {}),
|
|
167
|
+
...(result.toolCalls.length > 0 ? { toolCalls: result.toolCalls } : {})
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const event: RunEvent = {
|
|
171
|
+
type: "agent-turn",
|
|
172
|
+
runId,
|
|
173
|
+
at: new Date().toISOString(),
|
|
174
|
+
agentId: result.agent.id,
|
|
175
|
+
role: result.agent.role,
|
|
176
|
+
input: result.input,
|
|
177
|
+
output: result.response.text,
|
|
178
|
+
...(result.decision !== undefined ? { decision: result.decision } : {}),
|
|
179
|
+
cost: totalCost
|
|
180
|
+
};
|
|
181
|
+
emit(event);
|
|
182
|
+
recordProtocolDecision(event, {
|
|
183
|
+
turn: transcript.length,
|
|
184
|
+
phase: "worker",
|
|
185
|
+
transcriptEntryCount: transcript.length
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
stopIfNeeded();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!stopIfNeeded()) {
|
|
192
|
+
totalCost = await runCoordinatorTurn({
|
|
193
|
+
agent: coordinator,
|
|
194
|
+
coordinator,
|
|
195
|
+
input: buildFinalSynthesisInput(options.intent, transcript, coordinator),
|
|
196
|
+
phase: "final-synthesis",
|
|
197
|
+
options,
|
|
198
|
+
runId,
|
|
199
|
+
transcript,
|
|
200
|
+
totalCost,
|
|
201
|
+
providerCalls,
|
|
202
|
+
toolExecutor,
|
|
203
|
+
toolAvailability,
|
|
204
|
+
emit,
|
|
205
|
+
recordProtocolDecision
|
|
206
|
+
});
|
|
207
|
+
stopIfNeeded();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const output = transcript.at(-1)?.output ?? "";
|
|
212
|
+
throwIfAborted(options.signal, options.model.id);
|
|
213
|
+
const final: RunEvent = {
|
|
214
|
+
type: "final",
|
|
215
|
+
runId,
|
|
216
|
+
at: new Date().toISOString(),
|
|
217
|
+
output,
|
|
218
|
+
cost: totalCost,
|
|
219
|
+
transcript: createTranscriptLink(transcript),
|
|
220
|
+
...(termination !== undefined ? { termination } : {})
|
|
221
|
+
};
|
|
222
|
+
emit(final);
|
|
223
|
+
recordProtocolDecision(final, {
|
|
224
|
+
transcriptEntryCount: transcript.length
|
|
225
|
+
});
|
|
226
|
+
const finalEvent = events.at(-1);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
output,
|
|
230
|
+
eventLog: createRunEventLog(runId, "coordinator", events),
|
|
231
|
+
trace: {
|
|
232
|
+
schemaVersion: "1.0",
|
|
233
|
+
runId,
|
|
234
|
+
protocol: "coordinator",
|
|
235
|
+
tier: options.tier,
|
|
236
|
+
modelProviderId: options.model.id,
|
|
237
|
+
agentsUsed: activeAgents,
|
|
238
|
+
inputs: createReplayTraceRunInputs({
|
|
239
|
+
intent: options.intent,
|
|
240
|
+
protocol: options.protocol,
|
|
241
|
+
tier: options.tier,
|
|
242
|
+
modelProviderId: options.model.id,
|
|
243
|
+
agents: activeAgents,
|
|
244
|
+
temperature: options.temperature
|
|
245
|
+
}),
|
|
246
|
+
budget: createReplayTraceBudget({
|
|
247
|
+
tier: options.tier,
|
|
248
|
+
...(options.budget ? { caps: options.budget } : {}),
|
|
249
|
+
...(options.terminate ? { termination: options.terminate } : {})
|
|
250
|
+
}),
|
|
251
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
252
|
+
seed: createReplayTraceSeed(options.seed),
|
|
253
|
+
protocolDecisions,
|
|
254
|
+
providerCalls,
|
|
255
|
+
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
256
|
+
type: "final",
|
|
257
|
+
runId,
|
|
258
|
+
at: "",
|
|
259
|
+
output,
|
|
260
|
+
cost: totalCost,
|
|
261
|
+
transcript: createTranscriptLink(transcript)
|
|
262
|
+
}),
|
|
263
|
+
events,
|
|
264
|
+
transcript
|
|
265
|
+
},
|
|
266
|
+
transcript,
|
|
267
|
+
usage: createRunUsage(totalCost),
|
|
268
|
+
metadata: createRunMetadata({
|
|
269
|
+
runId,
|
|
270
|
+
protocol: "coordinator",
|
|
271
|
+
tier: options.tier,
|
|
272
|
+
modelProviderId: options.model.id,
|
|
273
|
+
agentsUsed: activeAgents,
|
|
274
|
+
events
|
|
275
|
+
}),
|
|
276
|
+
accounting: createRunAccounting({
|
|
277
|
+
tier: options.tier,
|
|
278
|
+
...(options.budget ? { budget: options.budget } : {}),
|
|
279
|
+
...(options.terminate ? { termination: options.terminate } : {}),
|
|
280
|
+
cost: totalCost,
|
|
281
|
+
events
|
|
282
|
+
}),
|
|
283
|
+
cost: totalCost
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
function stopIfNeeded(): boolean {
|
|
287
|
+
throwIfAborted(options.signal, options.model.id);
|
|
288
|
+
|
|
289
|
+
if (stopped || !options.terminate) {
|
|
290
|
+
return stopped;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const stopRecord = evaluateTerminationStop(options.terminate, {
|
|
294
|
+
runId,
|
|
295
|
+
protocol: "coordinator",
|
|
296
|
+
tier: options.tier,
|
|
297
|
+
cost: totalCost,
|
|
298
|
+
events,
|
|
299
|
+
transcript,
|
|
300
|
+
iteration: transcript.length,
|
|
301
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!stopRecord) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
stopped = true;
|
|
309
|
+
termination = stopRecord;
|
|
310
|
+
if (stopRecord.reason === "budget") {
|
|
311
|
+
emitBudgetStop(stopRecord);
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function emitBudgetStop(record: TerminationStopRecord): void {
|
|
317
|
+
const event: RunEvent = {
|
|
318
|
+
type: "budget-stop",
|
|
319
|
+
runId,
|
|
320
|
+
at: new Date().toISOString(),
|
|
321
|
+
reason: record.budgetReason ?? "cost",
|
|
322
|
+
cost: totalCost,
|
|
323
|
+
iteration: transcript.length,
|
|
324
|
+
elapsedMs: elapsedMs(startedAtMs),
|
|
325
|
+
detail: record.detail ?? {}
|
|
326
|
+
};
|
|
327
|
+
emit(event);
|
|
328
|
+
recordProtocolDecision(event, {
|
|
329
|
+
transcriptEntryCount: transcript.length
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
interface CoordinatorTurnOptions {
|
|
335
|
+
readonly agent: AgentSpec;
|
|
336
|
+
readonly coordinator: AgentSpec;
|
|
337
|
+
readonly input: string;
|
|
338
|
+
readonly phase: "plan" | "worker" | "final-synthesis";
|
|
339
|
+
readonly options: CoordinatorRunOptions;
|
|
340
|
+
readonly runId: string;
|
|
341
|
+
readonly transcript: TranscriptEntry[];
|
|
342
|
+
readonly totalCost: CostSummary;
|
|
343
|
+
readonly providerCalls: ReplayTraceProviderCall[];
|
|
344
|
+
readonly toolExecutor: RuntimeToolExecutor;
|
|
345
|
+
readonly toolAvailability: JsonObject;
|
|
346
|
+
readonly emit: (event: RunEvent) => void;
|
|
347
|
+
readonly recordProtocolDecision: (
|
|
348
|
+
event: RunEvent,
|
|
349
|
+
decisionOptions?: Parameters<typeof createReplayTraceProtocolDecision>[3]
|
|
350
|
+
) => void;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function runCoordinatorTurn(turn: CoordinatorTurnOptions): Promise<CostSummary> {
|
|
354
|
+
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
355
|
+
|
|
356
|
+
const request: ModelRequest = {
|
|
357
|
+
temperature: turn.options.temperature,
|
|
358
|
+
...(turn.options.signal !== undefined ? { signal: turn.options.signal } : {}),
|
|
359
|
+
metadata: {
|
|
360
|
+
runId: turn.runId,
|
|
361
|
+
protocol: "coordinator",
|
|
362
|
+
agentId: turn.agent.id,
|
|
363
|
+
role: turn.agent.role,
|
|
364
|
+
coordinatorAgentId: turn.coordinator.id,
|
|
365
|
+
tier: turn.options.tier,
|
|
366
|
+
phase: turn.phase,
|
|
367
|
+
...turn.toolAvailability
|
|
368
|
+
},
|
|
369
|
+
messages: [
|
|
370
|
+
{
|
|
371
|
+
role: "system",
|
|
372
|
+
content: buildSystemPrompt(turn.agent, turn.coordinator)
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
role: "user",
|
|
376
|
+
content: turn.input
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
};
|
|
380
|
+
const response = await generateModelTurn({
|
|
381
|
+
model: turn.options.model,
|
|
382
|
+
request,
|
|
383
|
+
runId: turn.runId,
|
|
384
|
+
agent: turn.agent,
|
|
385
|
+
input: turn.input,
|
|
386
|
+
emit: turn.emit,
|
|
387
|
+
callId: nextProviderCallId(turn.runId, turn.providerCalls),
|
|
388
|
+
onProviderCall(call): void {
|
|
389
|
+
turn.providerCalls.push(call);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
const decision = parseAgentDecision(response.text);
|
|
393
|
+
const totalCost = addCost(turn.totalCost, responseCost(response));
|
|
394
|
+
const toolCalls = await executeModelResponseToolRequests({
|
|
395
|
+
response,
|
|
396
|
+
executor: turn.toolExecutor,
|
|
397
|
+
agentId: turn.agent.id,
|
|
398
|
+
role: turn.agent.role,
|
|
399
|
+
turn: turn.transcript.length + 1,
|
|
400
|
+
metadata: {
|
|
401
|
+
phase: turn.phase
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
405
|
+
|
|
406
|
+
turn.transcript.push({
|
|
407
|
+
agentId: turn.agent.id,
|
|
408
|
+
role: turn.agent.role,
|
|
409
|
+
input: turn.input,
|
|
410
|
+
output: response.text,
|
|
411
|
+
...(decision !== undefined ? { decision } : {}),
|
|
412
|
+
...(toolCalls.length > 0 ? { toolCalls } : {})
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const event: RunEvent = {
|
|
416
|
+
type: "agent-turn",
|
|
417
|
+
runId: turn.runId,
|
|
418
|
+
at: new Date().toISOString(),
|
|
419
|
+
agentId: turn.agent.id,
|
|
420
|
+
role: turn.agent.role,
|
|
421
|
+
input: turn.input,
|
|
422
|
+
output: response.text,
|
|
423
|
+
...(decision !== undefined ? { decision } : {}),
|
|
424
|
+
cost: totalCost
|
|
425
|
+
};
|
|
426
|
+
turn.emit(event);
|
|
427
|
+
turn.recordProtocolDecision(event, {
|
|
428
|
+
turn: turn.transcript.length,
|
|
429
|
+
phase: turn.phase,
|
|
430
|
+
transcriptEntryCount: turn.transcript.length
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
return totalCost;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
interface CoordinatorWorkerTurnOptions {
|
|
437
|
+
readonly agent: AgentSpec;
|
|
438
|
+
readonly coordinator: AgentSpec;
|
|
439
|
+
readonly input: string;
|
|
440
|
+
readonly options: CoordinatorRunOptions;
|
|
441
|
+
readonly runId: string;
|
|
442
|
+
readonly turn: number;
|
|
443
|
+
readonly providerCallId: string;
|
|
444
|
+
readonly providerCallIndex: number;
|
|
445
|
+
readonly providerCallSlots: ReplayTraceProviderCall[];
|
|
446
|
+
readonly toolExecutor: RuntimeToolExecutor;
|
|
447
|
+
readonly toolAvailability: JsonObject;
|
|
448
|
+
readonly emit: (event: RunEvent) => void;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
interface CoordinatorWorkerTurnResult {
|
|
452
|
+
readonly agent: AgentSpec;
|
|
453
|
+
readonly input: string;
|
|
454
|
+
readonly response: ModelResponse;
|
|
455
|
+
readonly decision: ReturnType<typeof parseAgentDecision>;
|
|
456
|
+
readonly toolCalls: Awaited<ReturnType<typeof executeModelResponseToolRequests>>;
|
|
457
|
+
readonly turnCost: CostSummary;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async function runCoordinatorWorkerTurn(turn: CoordinatorWorkerTurnOptions): Promise<CoordinatorWorkerTurnResult> {
|
|
461
|
+
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
462
|
+
|
|
463
|
+
const request: ModelRequest = {
|
|
464
|
+
temperature: turn.options.temperature,
|
|
465
|
+
...(turn.options.signal !== undefined ? { signal: turn.options.signal } : {}),
|
|
466
|
+
metadata: {
|
|
467
|
+
runId: turn.runId,
|
|
468
|
+
protocol: "coordinator",
|
|
469
|
+
agentId: turn.agent.id,
|
|
470
|
+
role: turn.agent.role,
|
|
471
|
+
coordinatorAgentId: turn.coordinator.id,
|
|
472
|
+
tier: turn.options.tier,
|
|
473
|
+
phase: "worker",
|
|
474
|
+
...turn.toolAvailability
|
|
475
|
+
},
|
|
476
|
+
messages: [
|
|
477
|
+
{
|
|
478
|
+
role: "system",
|
|
479
|
+
content: buildSystemPrompt(turn.agent, turn.coordinator)
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
role: "user",
|
|
483
|
+
content: turn.input
|
|
484
|
+
}
|
|
485
|
+
]
|
|
486
|
+
};
|
|
487
|
+
const response = await generateModelTurn({
|
|
488
|
+
model: turn.options.model,
|
|
489
|
+
request,
|
|
490
|
+
runId: turn.runId,
|
|
491
|
+
agent: turn.agent,
|
|
492
|
+
input: turn.input,
|
|
493
|
+
emit: turn.emit,
|
|
494
|
+
callId: turn.providerCallId,
|
|
495
|
+
onProviderCall(call): void {
|
|
496
|
+
turn.providerCallSlots[turn.providerCallIndex] = call;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
const decision = parseAgentDecision(response.text);
|
|
500
|
+
const toolCalls = await executeModelResponseToolRequests({
|
|
501
|
+
response,
|
|
502
|
+
executor: turn.toolExecutor,
|
|
503
|
+
agentId: turn.agent.id,
|
|
504
|
+
role: turn.agent.role,
|
|
505
|
+
turn: turn.turn,
|
|
506
|
+
metadata: {
|
|
507
|
+
phase: "worker"
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
agent: turn.agent,
|
|
514
|
+
input: turn.input,
|
|
515
|
+
response,
|
|
516
|
+
decision,
|
|
517
|
+
toolCalls,
|
|
518
|
+
turnCost: responseCost(response)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function buildSystemPrompt(agent: AgentSpec, coordinator: AgentSpec | undefined): string {
|
|
523
|
+
const instruction = agent.instructions ? `\nInstructions: ${agent.instructions}` : "";
|
|
524
|
+
const coordinatorText =
|
|
525
|
+
coordinator && agent.id === coordinator.id
|
|
526
|
+
? "You are the coordinator: assign work, integrate worker contributions, and produce the final answer."
|
|
527
|
+
: `You are a worker managed by coordinator ${coordinator?.id ?? "unknown"}.`;
|
|
528
|
+
return `You are ${agent.id}, acting as ${agent.role} in a Coordinator multi-agent protocol. ${coordinatorText}${instruction}`;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function buildCoordinatorPlanInput(intent: string, coordinator: AgentSpec): string {
|
|
532
|
+
return `Mission: ${intent}\nCoordinator ${coordinator.id}: assign the work, name the plan, and provide the first contribution.`;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function buildWorkerInput(
|
|
536
|
+
intent: string,
|
|
537
|
+
transcript: readonly TranscriptEntry[],
|
|
538
|
+
coordinator: AgentSpec
|
|
539
|
+
): string {
|
|
540
|
+
const prior = transcript
|
|
541
|
+
.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`)
|
|
542
|
+
.join("\n\n");
|
|
543
|
+
return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nFollow the coordinator-managed plan and provide your assigned contribution.`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function buildFinalSynthesisInput(
|
|
547
|
+
intent: string,
|
|
548
|
+
transcript: readonly TranscriptEntry[],
|
|
549
|
+
coordinator: AgentSpec
|
|
550
|
+
): string {
|
|
551
|
+
const prior = transcript
|
|
552
|
+
.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`)
|
|
553
|
+
.join("\n\n");
|
|
554
|
+
return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nSynthesize the final answer as the coordinator.`;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function responseCost(response: ModelResponse): CostSummary {
|
|
558
|
+
return {
|
|
559
|
+
usd: response.costUsd ?? 0,
|
|
560
|
+
inputTokens: response.usage?.inputTokens ?? 0,
|
|
561
|
+
outputTokens: response.usage?.outputTokens ?? 0,
|
|
562
|
+
totalTokens: response.usage?.totalTokens ?? 0
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function createRunId(): string {
|
|
567
|
+
const random = globalThis.crypto?.randomUUID?.();
|
|
568
|
+
return random ?? `run-${Date.now().toString(36)}`;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function nowMs(): number {
|
|
572
|
+
return globalThis.performance?.now() ?? Date.now();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function elapsedMs(startedAtMs: number): number {
|
|
576
|
+
return Math.max(0, nowMs() - startedAtMs);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function providerCallIdFor(runId: string, oneBasedIndex: number): string {
|
|
580
|
+
return `${runId}:provider-call:${oneBasedIndex}`;
|
|
581
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AgentDecision, AgentParticipation } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export function parseAgentDecision(output: string): AgentDecision | undefined {
|
|
4
|
+
const selectedRole = matchLine(output, /^role_selected:\s*(.+)$/imu);
|
|
5
|
+
const participation = matchLine(output, /^participation:\s*(contribute|abstain)$/imu);
|
|
6
|
+
const rationale = matchLine(output, /^rationale:\s*(.+)$/imu);
|
|
7
|
+
const contribution = matchContribution(output);
|
|
8
|
+
|
|
9
|
+
if (!selectedRole || !participation || !isAgentParticipation(participation) || !rationale || !contribution) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
selectedRole,
|
|
15
|
+
participation,
|
|
16
|
+
rationale,
|
|
17
|
+
contribution
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isParticipatingDecision(decision: AgentDecision | undefined): boolean {
|
|
22
|
+
return decision?.participation !== "abstain";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function matchLine(output: string, pattern: RegExp): string | undefined {
|
|
26
|
+
const match = output.match(pattern);
|
|
27
|
+
return match?.[1]?.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function matchContribution(output: string): string | undefined {
|
|
31
|
+
const match = output.match(/^contribution:\s*\n([\s\S]*)$/imu);
|
|
32
|
+
const contribution = match?.[1]?.trim();
|
|
33
|
+
return contribution && contribution.length > 0 ? contribution : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isAgentParticipation(value: string): value is AgentParticipation {
|
|
37
|
+
return value === "contribute" || value === "abstain";
|
|
38
|
+
}
|