@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,355 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createDeterministicBroadcastTestMission } from "../internal.js";
|
|
3
|
+
import { run, runtimeToolManifest, stream } from "../index.js";
|
|
4
|
+
import type {
|
|
5
|
+
AgentSpec,
|
|
6
|
+
ConfiguredModelProvider,
|
|
7
|
+
JsonObject,
|
|
8
|
+
ModelRequest,
|
|
9
|
+
ModelResponse,
|
|
10
|
+
RuntimeTool
|
|
11
|
+
} from "../index.js";
|
|
12
|
+
|
|
13
|
+
describe("broadcast protocol", () => {
|
|
14
|
+
it("dispatches the mission to every configured seat and aggregates their responses", async () => {
|
|
15
|
+
const intent = "Assess whether the benchmark release should ship today.";
|
|
16
|
+
const agents: readonly AgentSpec[] = [
|
|
17
|
+
{
|
|
18
|
+
id: "seat-release",
|
|
19
|
+
role: "release",
|
|
20
|
+
instructions: "Focus on release blocking risk."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "seat-eval",
|
|
24
|
+
role: "evaluator",
|
|
25
|
+
instructions: "Focus on reproduction evidence."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "seat-api",
|
|
29
|
+
role: "api",
|
|
30
|
+
instructions: "Focus on caller ergonomics."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "seat-runtime",
|
|
34
|
+
role: "runtime",
|
|
35
|
+
instructions: "Focus on portability."
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
const requests: ModelRequest[] = [];
|
|
39
|
+
const model: ConfiguredModelProvider = {
|
|
40
|
+
id: "capturing-broadcast-model",
|
|
41
|
+
async generate(request: ModelRequest): Promise<ModelResponse> {
|
|
42
|
+
requests.push(request);
|
|
43
|
+
const agentId = String(request.metadata.agentId);
|
|
44
|
+
const role = String(request.metadata.role);
|
|
45
|
+
return {
|
|
46
|
+
text: `${agentId}(${role}) saw the shared broadcast mission`,
|
|
47
|
+
usage: {
|
|
48
|
+
inputTokens: 7,
|
|
49
|
+
outputTokens: 5,
|
|
50
|
+
totalTokens: 12
|
|
51
|
+
},
|
|
52
|
+
costUsd: 0.001
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const result = await run({
|
|
58
|
+
intent,
|
|
59
|
+
protocol: { kind: "broadcast", maxRounds: 1 },
|
|
60
|
+
tier: "balanced",
|
|
61
|
+
model,
|
|
62
|
+
agents
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(requests).toHaveLength(agents.length);
|
|
66
|
+
for (const [index, agent] of agents.entries()) {
|
|
67
|
+
const request = requests[index];
|
|
68
|
+
if (!request) {
|
|
69
|
+
throw new Error(`missing broadcast request for ${agent.id}`);
|
|
70
|
+
}
|
|
71
|
+
const userMessage = request.messages.find((message) => message.role === "user");
|
|
72
|
+
const systemMessage = request.messages.find((message) => message.role === "system");
|
|
73
|
+
|
|
74
|
+
expect(request.metadata).toMatchObject({
|
|
75
|
+
protocol: "broadcast",
|
|
76
|
+
agentId: agent.id,
|
|
77
|
+
role: agent.role,
|
|
78
|
+
tier: "balanced",
|
|
79
|
+
round: 1
|
|
80
|
+
});
|
|
81
|
+
expect(userMessage?.content).toContain(intent);
|
|
82
|
+
expect(userMessage?.content).toContain("Broadcast round 1");
|
|
83
|
+
expect(systemMessage?.content).toContain(agent.id);
|
|
84
|
+
expect(systemMessage?.content).toContain(agent.role);
|
|
85
|
+
expect(systemMessage?.content).toContain(agent.instructions);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const expectedOutputs = agents.map(
|
|
89
|
+
(agent) => `${agent.id}(${agent.role}) saw the shared broadcast mission`
|
|
90
|
+
);
|
|
91
|
+
expect(result.transcript.map((entry) => entry.output)).toEqual(expectedOutputs);
|
|
92
|
+
expect(result.output).toBe(
|
|
93
|
+
agents
|
|
94
|
+
.map((agent, index) => `${agent.role}:${agent.id} => ${expectedOutputs[index]}`)
|
|
95
|
+
.join("\n")
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const broadcastEvent = result.trace.events.find((event) => event.type === "broadcast");
|
|
99
|
+
expect(broadcastEvent?.type).toBe("broadcast");
|
|
100
|
+
if (broadcastEvent?.type !== "broadcast") {
|
|
101
|
+
throw new Error("expected broadcast event");
|
|
102
|
+
}
|
|
103
|
+
expect(broadcastEvent.contributions.map((contribution) => contribution.output)).toEqual(expectedOutputs);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("feeds round-one intentions into round-two final broadcast decisions", async () => {
|
|
107
|
+
const requests: ModelRequest[] = [];
|
|
108
|
+
const model: ConfiguredModelProvider = {
|
|
109
|
+
id: "two-round-broadcast-model",
|
|
110
|
+
async generate(request) {
|
|
111
|
+
requests.push(request);
|
|
112
|
+
const round = Number(request.metadata.round);
|
|
113
|
+
const agentId = String(request.metadata.agentId);
|
|
114
|
+
const selectedRole = agentId === "agent-a" ? "upload supervisor" : "release verifier";
|
|
115
|
+
if (round === 1) {
|
|
116
|
+
return {
|
|
117
|
+
text: [
|
|
118
|
+
`role_selected: ${selectedRole}`,
|
|
119
|
+
"participation: contribute",
|
|
120
|
+
"rationale: I am broadcasting my intended specialization before final decisions.",
|
|
121
|
+
"contribution:",
|
|
122
|
+
`Intention from ${agentId}: cover ${selectedRole}.`
|
|
123
|
+
].join("\n")
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
text: [
|
|
129
|
+
`role_selected: ${selectedRole}`,
|
|
130
|
+
"participation: contribute",
|
|
131
|
+
"rationale: I saw the round one intentions and can now make a final contribution.",
|
|
132
|
+
"contribution:",
|
|
133
|
+
`Final contribution from ${agentId}.`
|
|
134
|
+
].join("\n")
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = await run({
|
|
140
|
+
intent: "Plan an upload manager.",
|
|
141
|
+
protocol: { kind: "broadcast", maxRounds: 2 },
|
|
142
|
+
tier: "fast",
|
|
143
|
+
model,
|
|
144
|
+
agents: [
|
|
145
|
+
{ id: "agent-a", role: "autonomous-agent" },
|
|
146
|
+
{ id: "agent-b", role: "autonomous-agent" }
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
expect(requests).toHaveLength(4);
|
|
151
|
+
const roundTwoPrompts = requests.slice(2).map((request) => request.messages.find((message) => message.role === "user")?.content);
|
|
152
|
+
expect(roundTwoPrompts[0]).toContain("Round 1 intentions:");
|
|
153
|
+
expect(roundTwoPrompts[0]).toContain("Intention from agent-a");
|
|
154
|
+
expect(roundTwoPrompts[0]).toContain("Intention from agent-b");
|
|
155
|
+
expect(roundTwoPrompts[1]).toContain("Intention from agent-a");
|
|
156
|
+
expect(roundTwoPrompts[1]).toContain("Intention from agent-b");
|
|
157
|
+
expect(result.output).toContain("Final contribution from agent-a.");
|
|
158
|
+
expect(result.output).toContain("Final contribution from agent-b.");
|
|
159
|
+
expect(result.output).not.toContain("Intention from agent-a");
|
|
160
|
+
expect(result.transcript).toHaveLength(4);
|
|
161
|
+
expect(result.transcript[0]?.decision).toMatchObject({
|
|
162
|
+
selectedRole: "upload supervisor",
|
|
163
|
+
participation: "contribute"
|
|
164
|
+
});
|
|
165
|
+
const broadcastEvents = result.trace.events.filter((event) => event.type === "broadcast");
|
|
166
|
+
expect(broadcastEvents).toHaveLength(2);
|
|
167
|
+
expect(broadcastEvents[1]?.type).toBe("broadcast");
|
|
168
|
+
if (broadcastEvents[1]?.type !== "broadcast") {
|
|
169
|
+
throw new Error("expected second broadcast event");
|
|
170
|
+
}
|
|
171
|
+
expect(broadcastEvents[1].contributions.map((contribution) => contribution.output)).toEqual([
|
|
172
|
+
[
|
|
173
|
+
"role_selected: upload supervisor",
|
|
174
|
+
"participation: contribute",
|
|
175
|
+
"rationale: I saw the round one intentions and can now make a final contribution.",
|
|
176
|
+
"contribution:",
|
|
177
|
+
"Final contribution from agent-a."
|
|
178
|
+
].join("\n"),
|
|
179
|
+
[
|
|
180
|
+
"role_selected: release verifier",
|
|
181
|
+
"participation: contribute",
|
|
182
|
+
"rationale: I saw the round one intentions and can now make a final contribution.",
|
|
183
|
+
"contribution:",
|
|
184
|
+
"Final contribution from agent-b."
|
|
185
|
+
].join("\n")
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("runs a deterministic test mission against a configured model provider", async () => {
|
|
190
|
+
const result = await run(createDeterministicBroadcastTestMission());
|
|
191
|
+
const expectedTranscript = [
|
|
192
|
+
{
|
|
193
|
+
agentId: "agent-1",
|
|
194
|
+
role: "release-engineer",
|
|
195
|
+
input:
|
|
196
|
+
"Mission: Decide whether to ship a portable multi-agent SDK release candidate.\nBroadcast round 1: contribute independently before synthesis.",
|
|
197
|
+
output: "release-engineer:agent-1 independently assessed the broadcast mission."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
agentId: "agent-2",
|
|
201
|
+
role: "paper-reviewer",
|
|
202
|
+
input:
|
|
203
|
+
"Mission: Decide whether to ship a portable multi-agent SDK release candidate.\nBroadcast round 1: contribute independently before synthesis.",
|
|
204
|
+
output: "paper-reviewer:agent-2 independently assessed the broadcast mission."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
agentId: "agent-3",
|
|
208
|
+
role: "developer-advocate",
|
|
209
|
+
input:
|
|
210
|
+
"Mission: Decide whether to ship a portable multi-agent SDK release candidate.\nBroadcast round 1: contribute independently before synthesis.",
|
|
211
|
+
output: "developer-advocate:agent-3 independently assessed the broadcast mission."
|
|
212
|
+
}
|
|
213
|
+
] as const;
|
|
214
|
+
const expectedOutput = expectedTranscript
|
|
215
|
+
.map((entry) => `${entry.role}:${entry.agentId} => ${entry.output}`)
|
|
216
|
+
.join("\n");
|
|
217
|
+
|
|
218
|
+
expect(result.output).toBe(expectedOutput);
|
|
219
|
+
expect(result.transcript).toEqual(expectedTranscript);
|
|
220
|
+
expect(result.trace.transcript).toEqual(expectedTranscript);
|
|
221
|
+
expect(result.trace.transcript).toEqual(result.transcript);
|
|
222
|
+
expect(result.trace.protocol).toBe("broadcast");
|
|
223
|
+
expect(result.trace.modelProviderId).toBe("deterministic-broadcast-model");
|
|
224
|
+
expect(result.trace.events.map((event) => event.type)).toEqual([
|
|
225
|
+
"role-assignment",
|
|
226
|
+
"role-assignment",
|
|
227
|
+
"role-assignment",
|
|
228
|
+
"agent-turn",
|
|
229
|
+
"agent-turn",
|
|
230
|
+
"agent-turn",
|
|
231
|
+
"broadcast",
|
|
232
|
+
"final"
|
|
233
|
+
]);
|
|
234
|
+
for (const event of result.trace.events) {
|
|
235
|
+
expect(event.runId).toBe(result.trace.runId);
|
|
236
|
+
expect(new Date(event.at).toISOString()).toBe(event.at);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const broadcastEvent = result.trace.events.find((event) => event.type === "broadcast");
|
|
240
|
+
expect(broadcastEvent?.type).toBe("broadcast");
|
|
241
|
+
if (broadcastEvent?.type !== "broadcast") {
|
|
242
|
+
throw new Error("expected broadcast event");
|
|
243
|
+
}
|
|
244
|
+
expect(broadcastEvent.round).toBe(1);
|
|
245
|
+
expect(broadcastEvent.contributions).toHaveLength(3);
|
|
246
|
+
expect(broadcastEvent.contributions.map((contribution) => contribution.agentId)).toEqual([
|
|
247
|
+
"agent-1",
|
|
248
|
+
"agent-2",
|
|
249
|
+
"agent-3"
|
|
250
|
+
]);
|
|
251
|
+
expect(broadcastEvent.contributions).toEqual(
|
|
252
|
+
expectedTranscript.map((entry) => ({
|
|
253
|
+
agentId: entry.agentId,
|
|
254
|
+
role: entry.role,
|
|
255
|
+
output: entry.output
|
|
256
|
+
}))
|
|
257
|
+
);
|
|
258
|
+
const finalEvent = result.trace.events.at(-1);
|
|
259
|
+
expect(finalEvent?.type).toBe("final");
|
|
260
|
+
if (finalEvent?.type !== "final") {
|
|
261
|
+
throw new Error("expected final event");
|
|
262
|
+
}
|
|
263
|
+
expect(finalEvent.output).toBe(expectedOutput);
|
|
264
|
+
expect(finalEvent.cost).toEqual(result.cost);
|
|
265
|
+
expect(JSON.parse(JSON.stringify(result.trace))).toEqual(result.trace);
|
|
266
|
+
expect(result.cost.totalTokens).toBeGreaterThan(0);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("streams broadcast coordination moments before resolving the final result", async () => {
|
|
270
|
+
const handle = stream(createDeterministicBroadcastTestMission());
|
|
271
|
+
|
|
272
|
+
const events: string[] = [];
|
|
273
|
+
for await (const event of handle) {
|
|
274
|
+
events.push(event.type);
|
|
275
|
+
}
|
|
276
|
+
const result = await handle.result;
|
|
277
|
+
|
|
278
|
+
expect(events).toEqual([
|
|
279
|
+
"role-assignment",
|
|
280
|
+
"role-assignment",
|
|
281
|
+
"role-assignment",
|
|
282
|
+
"agent-turn",
|
|
283
|
+
"agent-turn",
|
|
284
|
+
"agent-turn",
|
|
285
|
+
"broadcast",
|
|
286
|
+
"final"
|
|
287
|
+
]);
|
|
288
|
+
expect(result.output).toContain("developer-advocate:agent-3");
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("threads runtime tool availability through every broadcast model turn", async () => {
|
|
292
|
+
interface LookupInput extends JsonObject {
|
|
293
|
+
readonly query: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
interface LookupOutput extends JsonObject {
|
|
297
|
+
readonly answer: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const requests: ModelRequest[] = [];
|
|
301
|
+
const lookupTool: RuntimeTool<LookupInput, LookupOutput> = {
|
|
302
|
+
identity: {
|
|
303
|
+
id: "fixture.lookup",
|
|
304
|
+
name: "lookup",
|
|
305
|
+
description: "Lookup contextual facts for the active mission."
|
|
306
|
+
},
|
|
307
|
+
inputSchema: {
|
|
308
|
+
kind: "json-schema",
|
|
309
|
+
schema: {
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
query: { type: "string" }
|
|
313
|
+
},
|
|
314
|
+
required: ["query"],
|
|
315
|
+
additionalProperties: false
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
execute(input, context) {
|
|
319
|
+
return {
|
|
320
|
+
type: "success",
|
|
321
|
+
toolCallId: context.toolCallId,
|
|
322
|
+
tool: this.identity,
|
|
323
|
+
output: {
|
|
324
|
+
answer: `found:${input.query}`
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const model: ConfiguredModelProvider = {
|
|
330
|
+
id: "broadcast-tool-availability-model",
|
|
331
|
+
async generate(request) {
|
|
332
|
+
requests.push(request);
|
|
333
|
+
return { text: `turn-${requests.length}` };
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
await run({
|
|
338
|
+
intent: "Use available tools while independently assessing a release.",
|
|
339
|
+
protocol: { kind: "broadcast", maxRounds: 1 },
|
|
340
|
+
tier: "fast",
|
|
341
|
+
model,
|
|
342
|
+
agents: [
|
|
343
|
+
{ id: "release-seat", role: "release" },
|
|
344
|
+
{ id: "eval-seat", role: "evaluator" }
|
|
345
|
+
],
|
|
346
|
+
tools: [lookupTool]
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(requests).toHaveLength(2);
|
|
350
|
+
expect(requests.map((request) => request.metadata.tools)).toEqual([
|
|
351
|
+
runtimeToolManifest([lookupTool]),
|
|
352
|
+
runtimeToolManifest([lookupTool])
|
|
353
|
+
]);
|
|
354
|
+
});
|
|
355
|
+
});
|