@dogpile/sdk 0.1.2 → 0.2.1
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 +16 -0
- package/README.md +9 -7
- package/dist/browser/index.js +3773 -4061
- package/dist/browser/index.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +51 -8
- package/dist/providers/openai-compatible.js.map +1 -1
- package/package.json +19 -7
- package/src/providers/openai-compatible.ts +62 -8
- package/src/runtime/broadcast.test.ts +0 -355
- package/src/runtime/coordinator.test.ts +0 -468
- package/src/runtime/sequential.test.ts +0 -262
- package/src/runtime/shared.test.ts +0 -265
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createDeterministicModelProvider } from "../internal.js";
|
|
3
|
-
import { Dogpile, run, runtimeToolManifest, stream } from "../index.js";
|
|
4
|
-
import type { ConfiguredModelProvider, JsonObject, ModelRequest, RunEvent, RuntimeTool } from "../index.js";
|
|
5
|
-
|
|
6
|
-
describe("sequential protocol", () => {
|
|
7
|
-
it("uses the ergonomic default flow when protocol and tier are omitted", async () => {
|
|
8
|
-
const result = await Dogpile.pile({
|
|
9
|
-
intent: "Draft a release note for a portable multi-agent SDK.",
|
|
10
|
-
model: createDeterministicModelProvider("default-flow-model")
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
expect(result.output).toContain("synthesizer:agent-3");
|
|
14
|
-
expect(result.transcript).toHaveLength(3);
|
|
15
|
-
expect(result.trace.protocol).toBe("sequential");
|
|
16
|
-
expect(result.trace.tier).toBe("balanced");
|
|
17
|
-
expect(result.trace.modelProviderId).toBe("default-flow-model");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("runs end-to-end against a configured model provider", async () => {
|
|
21
|
-
const result = await run({
|
|
22
|
-
intent: "Draft a release note for a portable multi-agent SDK.",
|
|
23
|
-
protocol: "sequential",
|
|
24
|
-
tier: "fast",
|
|
25
|
-
model: createDeterministicModelProvider()
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
expect(result.output).toContain("synthesizer:agent-3");
|
|
29
|
-
expect(result.transcript).toHaveLength(3);
|
|
30
|
-
expect(result.trace.protocol).toBe("sequential");
|
|
31
|
-
expect(result.trace.modelProviderId).toBe("deterministic-test-model");
|
|
32
|
-
expect(result.trace.events.map((event) => event.type)).toEqual([
|
|
33
|
-
"role-assignment",
|
|
34
|
-
"role-assignment",
|
|
35
|
-
"role-assignment",
|
|
36
|
-
"agent-turn",
|
|
37
|
-
"agent-turn",
|
|
38
|
-
"agent-turn",
|
|
39
|
-
"final"
|
|
40
|
-
]);
|
|
41
|
-
expect(JSON.parse(JSON.stringify(result.trace))).toEqual(result.trace);
|
|
42
|
-
expect(result.cost.totalTokens).toBeGreaterThan(0);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("passes a caller AbortSignal through every sequential model request", async () => {
|
|
46
|
-
const abortController = new AbortController();
|
|
47
|
-
const requests: ModelRequest[] = [];
|
|
48
|
-
const model: ConfiguredModelProvider = {
|
|
49
|
-
id: "abort-signal-model",
|
|
50
|
-
async generate(request) {
|
|
51
|
-
requests.push(request);
|
|
52
|
-
return { text: `turn-${requests.length}` };
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const result = await run({
|
|
57
|
-
intent: "Verify cancellation plumbing reaches the provider adapter.",
|
|
58
|
-
protocol: { kind: "sequential", maxTurns: 2 },
|
|
59
|
-
tier: "fast",
|
|
60
|
-
model,
|
|
61
|
-
signal: abortController.signal
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
expect(requests).toHaveLength(2);
|
|
65
|
-
expect(requests.map((request) => request.signal)).toEqual([
|
|
66
|
-
abortController.signal,
|
|
67
|
-
abortController.signal
|
|
68
|
-
]);
|
|
69
|
-
expect(result.trace.providerCalls.map((call) => call.request.signal)).toEqual([
|
|
70
|
-
undefined,
|
|
71
|
-
undefined
|
|
72
|
-
]);
|
|
73
|
-
expect(JSON.parse(JSON.stringify(result.trace))).toEqual(result.trace);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("records autonomous decisions and skips abstentions when choosing the final output", async () => {
|
|
77
|
-
const responses = [
|
|
78
|
-
[
|
|
79
|
-
"role_selected: upload workflow analyst",
|
|
80
|
-
"participation: contribute",
|
|
81
|
-
"rationale: This starts the plan with the user journey.",
|
|
82
|
-
"contribution:",
|
|
83
|
-
"Contribution from the first agent."
|
|
84
|
-
].join("\n"),
|
|
85
|
-
[
|
|
86
|
-
"role_selected: duplicate reviewer",
|
|
87
|
-
"participation: abstain",
|
|
88
|
-
"rationale: The prior output already covers this slice.",
|
|
89
|
-
"contribution:",
|
|
90
|
-
"No additional contribution is needed."
|
|
91
|
-
].join("\n")
|
|
92
|
-
];
|
|
93
|
-
const model: ConfiguredModelProvider = {
|
|
94
|
-
id: "sequential-decision-model",
|
|
95
|
-
async generate() {
|
|
96
|
-
return { text: responses.shift() ?? "unused" };
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const result = await run({
|
|
101
|
-
intent: "Plan a Hugging Face upload GUI.",
|
|
102
|
-
protocol: { kind: "sequential", maxTurns: 2 },
|
|
103
|
-
tier: "fast",
|
|
104
|
-
model,
|
|
105
|
-
agents: [
|
|
106
|
-
{ id: "agent-0", role: "autonomous-agent" },
|
|
107
|
-
{ id: "agent-1", role: "autonomous-agent" }
|
|
108
|
-
]
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(result.output).toContain("Contribution from the first agent.");
|
|
112
|
-
expect(result.output).not.toContain("No additional contribution is needed.");
|
|
113
|
-
expect(result.transcript[0]?.decision).toMatchObject({
|
|
114
|
-
selectedRole: "upload workflow analyst",
|
|
115
|
-
participation: "contribute"
|
|
116
|
-
});
|
|
117
|
-
expect(result.transcript[1]?.decision).toMatchObject({
|
|
118
|
-
selectedRole: "duplicate reviewer",
|
|
119
|
-
participation: "abstain"
|
|
120
|
-
});
|
|
121
|
-
const turnEvents = result.trace.events.filter((event) => event.type === "agent-turn");
|
|
122
|
-
expect(turnEvents[1]?.type).toBe("agent-turn");
|
|
123
|
-
if (turnEvents[1]?.type !== "agent-turn") {
|
|
124
|
-
throw new Error("expected second turn event");
|
|
125
|
-
}
|
|
126
|
-
expect(turnEvents[1].decision?.participation).toBe("abstain");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("streams the same coordination moments before resolving the final result", async () => {
|
|
130
|
-
const handle = stream({
|
|
131
|
-
intent: "Summarize the value of sequential agent collaboration.",
|
|
132
|
-
protocol: { kind: "sequential", maxTurns: 2 },
|
|
133
|
-
tier: "balanced",
|
|
134
|
-
model: createDeterministicModelProvider("configured-stream-model")
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const events = [];
|
|
138
|
-
for await (const event of handle) {
|
|
139
|
-
events.push(event.type);
|
|
140
|
-
}
|
|
141
|
-
const result = await handle.result;
|
|
142
|
-
|
|
143
|
-
expect(events).toEqual([
|
|
144
|
-
"role-assignment",
|
|
145
|
-
"role-assignment",
|
|
146
|
-
"agent-turn",
|
|
147
|
-
"agent-turn",
|
|
148
|
-
"final"
|
|
149
|
-
]);
|
|
150
|
-
expect(result.output).toContain("critic:agent-2");
|
|
151
|
-
expect(result.trace.modelProviderId).toBe("configured-stream-model");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("streams role-assignment events with agent ids and roles before agent work events", async () => {
|
|
155
|
-
const handle = Dogpile.stream({
|
|
156
|
-
intent: "Verify role assignment streaming before work starts.",
|
|
157
|
-
protocol: { kind: "sequential", maxTurns: 2 },
|
|
158
|
-
tier: "balanced",
|
|
159
|
-
model: createDeterministicModelProvider("role-stream-model"),
|
|
160
|
-
agents: [
|
|
161
|
-
{ id: "planner-seat", role: "planner" },
|
|
162
|
-
{ id: "reviewer-seat", role: "reviewer" }
|
|
163
|
-
]
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const streamedEvents: RunEvent[] = [];
|
|
167
|
-
for await (const event of handle) {
|
|
168
|
-
if (event.type !== "error") {
|
|
169
|
-
streamedEvents.push(event as RunEvent);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const result = await handle.result;
|
|
173
|
-
|
|
174
|
-
expect(streamedEvents.map((event) => event.type)).toEqual([
|
|
175
|
-
"role-assignment",
|
|
176
|
-
"role-assignment",
|
|
177
|
-
"agent-turn",
|
|
178
|
-
"agent-turn",
|
|
179
|
-
"final"
|
|
180
|
-
]);
|
|
181
|
-
expect(streamedEvents.slice(0, 2)).toEqual([
|
|
182
|
-
expect.objectContaining({
|
|
183
|
-
type: "role-assignment",
|
|
184
|
-
runId: result.trace.runId,
|
|
185
|
-
agentId: "planner-seat",
|
|
186
|
-
role: "planner"
|
|
187
|
-
}),
|
|
188
|
-
expect.objectContaining({
|
|
189
|
-
type: "role-assignment",
|
|
190
|
-
runId: result.trace.runId,
|
|
191
|
-
agentId: "reviewer-seat",
|
|
192
|
-
role: "reviewer"
|
|
193
|
-
})
|
|
194
|
-
]);
|
|
195
|
-
expect(result.trace.events).toEqual(streamedEvents);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("threads runtime tool availability through every sequential model turn", async () => {
|
|
199
|
-
interface LookupInput extends JsonObject {
|
|
200
|
-
readonly query: string;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
interface LookupOutput extends JsonObject {
|
|
204
|
-
readonly answer: string;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const requests: ModelRequest[] = [];
|
|
208
|
-
const lookupTool: RuntimeTool<LookupInput, LookupOutput> = {
|
|
209
|
-
identity: {
|
|
210
|
-
id: "fixture.lookup",
|
|
211
|
-
name: "lookup",
|
|
212
|
-
description: "Lookup contextual facts for the active mission."
|
|
213
|
-
},
|
|
214
|
-
inputSchema: {
|
|
215
|
-
kind: "json-schema",
|
|
216
|
-
schema: {
|
|
217
|
-
type: "object",
|
|
218
|
-
properties: {
|
|
219
|
-
query: { type: "string" }
|
|
220
|
-
},
|
|
221
|
-
required: ["query"],
|
|
222
|
-
additionalProperties: false
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
execute(input, context) {
|
|
226
|
-
return {
|
|
227
|
-
type: "success",
|
|
228
|
-
toolCallId: context.toolCallId,
|
|
229
|
-
tool: this.identity,
|
|
230
|
-
output: {
|
|
231
|
-
answer: `found:${input.query}`
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
const model: ConfiguredModelProvider = {
|
|
237
|
-
id: "sequential-tool-availability-model",
|
|
238
|
-
async generate(request) {
|
|
239
|
-
requests.push(request);
|
|
240
|
-
return { text: `turn-${requests.length}` };
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
await run({
|
|
245
|
-
intent: "Use available tools while composing a release note.",
|
|
246
|
-
protocol: { kind: "sequential", maxTurns: 2 },
|
|
247
|
-
tier: "fast",
|
|
248
|
-
model,
|
|
249
|
-
agents: [
|
|
250
|
-
{ id: "researcher-seat", role: "researcher" },
|
|
251
|
-
{ id: "writer-seat", role: "writer" }
|
|
252
|
-
],
|
|
253
|
-
tools: [lookupTool]
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
expect(requests).toHaveLength(2);
|
|
257
|
-
expect(requests.map((request) => request.metadata.tools)).toEqual([
|
|
258
|
-
runtimeToolManifest([lookupTool]),
|
|
259
|
-
runtimeToolManifest([lookupTool])
|
|
260
|
-
]);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createDeterministicSharedTestMission } from "../internal.js";
|
|
3
|
-
import { Dogpile, run, runtimeToolManifest } from "../index.js";
|
|
4
|
-
import type {
|
|
5
|
-
ConfiguredModelProvider,
|
|
6
|
-
JsonObject,
|
|
7
|
-
ModelRequest,
|
|
8
|
-
ModelResponse,
|
|
9
|
-
RunEvent,
|
|
10
|
-
RuntimeTool
|
|
11
|
-
} from "../index.js";
|
|
12
|
-
|
|
13
|
-
describe("shared protocol", () => {
|
|
14
|
-
it("executes end-to-end through the branded high-level SDK call", async () => {
|
|
15
|
-
const result = await Dogpile.pile(createDeterministicSharedTestMission());
|
|
16
|
-
|
|
17
|
-
expect(result.output).toBe(
|
|
18
|
-
[
|
|
19
|
-
"state-initializer:agent-1 => state-initializer:agent-1 initialized the shared state.",
|
|
20
|
-
"state-reviewer:agent-2 => state-reviewer:agent-2 initialized the shared state.",
|
|
21
|
-
"state-synthesizer:agent-3 => state-synthesizer:agent-3 initialized the shared state."
|
|
22
|
-
].join("\n")
|
|
23
|
-
);
|
|
24
|
-
expect(result.trace.protocol).toBe("shared");
|
|
25
|
-
expect(result.trace.modelProviderId).toBe("deterministic-shared-model");
|
|
26
|
-
expect(result.trace.events.map((event) => event.type)).toEqual([
|
|
27
|
-
"role-assignment",
|
|
28
|
-
"role-assignment",
|
|
29
|
-
"role-assignment",
|
|
30
|
-
"agent-turn",
|
|
31
|
-
"agent-turn",
|
|
32
|
-
"agent-turn",
|
|
33
|
-
"final"
|
|
34
|
-
]);
|
|
35
|
-
expect(result.transcript).toHaveLength(3);
|
|
36
|
-
expect(result.trace.transcript).toEqual(result.transcript);
|
|
37
|
-
expect(JSON.parse(JSON.stringify(result.trace))).toEqual(result.trace);
|
|
38
|
-
expect(result.cost.totalTokens).toBeGreaterThan(0);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("runs a deterministic shared-state test mission against the configured model provider", async () => {
|
|
42
|
-
const requests: ModelRequest[] = [];
|
|
43
|
-
const model: ConfiguredModelProvider = {
|
|
44
|
-
id: "configured-shared-model",
|
|
45
|
-
async generate(request: ModelRequest): Promise<ModelResponse> {
|
|
46
|
-
requests.push(request);
|
|
47
|
-
const agentId = String(request.metadata.agentId);
|
|
48
|
-
const role = String(request.metadata.role);
|
|
49
|
-
const turn = Number(request.metadata.turn);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
text: `${role}:${agentId} wrote shared turn ${turn}.`,
|
|
53
|
-
usage: {
|
|
54
|
-
inputTokens: 9,
|
|
55
|
-
outputTokens: 5,
|
|
56
|
-
totalTokens: 14
|
|
57
|
-
},
|
|
58
|
-
costUsd: 0.001
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const result = await run(createDeterministicSharedTestMission(model));
|
|
64
|
-
|
|
65
|
-
expect(requests).toHaveLength(3);
|
|
66
|
-
expect(result.trace.protocol).toBe("shared");
|
|
67
|
-
expect(result.trace.modelProviderId).toBe("configured-shared-model");
|
|
68
|
-
expect(result.trace.events.map((event) => event.type)).toEqual([
|
|
69
|
-
"role-assignment",
|
|
70
|
-
"role-assignment",
|
|
71
|
-
"role-assignment",
|
|
72
|
-
"agent-turn",
|
|
73
|
-
"agent-turn",
|
|
74
|
-
"agent-turn",
|
|
75
|
-
"final"
|
|
76
|
-
]);
|
|
77
|
-
|
|
78
|
-
for (const [index, request] of requests.entries()) {
|
|
79
|
-
const turn = index + 1;
|
|
80
|
-
const userMessage = request.messages.find((message) => message.role === "user");
|
|
81
|
-
const systemMessage = request.messages.find((message) => message.role === "system");
|
|
82
|
-
|
|
83
|
-
expect(request.metadata).toMatchObject({
|
|
84
|
-
protocol: "shared",
|
|
85
|
-
tier: "fast",
|
|
86
|
-
turn
|
|
87
|
-
});
|
|
88
|
-
expect(userMessage?.content).toContain("Decide whether the shared protocol can support portable replay.");
|
|
89
|
-
expect(userMessage?.content).toContain(`Shared turn ${turn}`);
|
|
90
|
-
expect(userMessage?.content).toContain("Shared state:");
|
|
91
|
-
expect(systemMessage?.content).toContain("Shared multi-agent protocol");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
expect(requests[0]?.messages.find((message) => message.role === "user")?.content).toContain("(empty)");
|
|
95
|
-
expect(requests[1]?.messages.find((message) => message.role === "user")?.content).toContain("(empty)");
|
|
96
|
-
expect(requests[2]?.messages.find((message) => message.role === "user")?.content).toContain("(empty)");
|
|
97
|
-
expect(requests[1]?.messages.find((message) => message.role === "user")?.content).not.toContain(
|
|
98
|
-
"state-initializer:agent-1 =>"
|
|
99
|
-
);
|
|
100
|
-
expect(result.output).toBe(
|
|
101
|
-
[
|
|
102
|
-
"state-initializer:agent-1 => state-initializer:agent-1 wrote shared turn 1.",
|
|
103
|
-
"state-reviewer:agent-2 => state-reviewer:agent-2 wrote shared turn 2.",
|
|
104
|
-
"state-synthesizer:agent-3 => state-synthesizer:agent-3 wrote shared turn 3."
|
|
105
|
-
].join("\n")
|
|
106
|
-
);
|
|
107
|
-
expect(result.transcript).toHaveLength(3);
|
|
108
|
-
expect(result.trace.transcript).toEqual(result.transcript);
|
|
109
|
-
expect(JSON.parse(JSON.stringify(result.trace))).toEqual(result.trace);
|
|
110
|
-
expect(result.cost.totalTokens).toBe(42);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("streams shared coordination events through the high-level SDK handle", async () => {
|
|
114
|
-
const handle = Dogpile.stream(createDeterministicSharedTestMission());
|
|
115
|
-
|
|
116
|
-
const streamedEvents: RunEvent[] = [];
|
|
117
|
-
for await (const event of handle) {
|
|
118
|
-
if (event.type !== "error") {
|
|
119
|
-
streamedEvents.push(event as RunEvent);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const result = await handle.result;
|
|
123
|
-
|
|
124
|
-
const expectedOutput = [
|
|
125
|
-
"state-initializer:agent-1 => state-initializer:agent-1 initialized the shared state.",
|
|
126
|
-
"state-reviewer:agent-2 => state-reviewer:agent-2 initialized the shared state.",
|
|
127
|
-
"state-synthesizer:agent-3 => state-synthesizer:agent-3 initialized the shared state."
|
|
128
|
-
].join("\n");
|
|
129
|
-
|
|
130
|
-
expect(result.output).toBe(expectedOutput);
|
|
131
|
-
expect(streamedEvents.map((event) => event.type)).toEqual([
|
|
132
|
-
"role-assignment",
|
|
133
|
-
"role-assignment",
|
|
134
|
-
"role-assignment",
|
|
135
|
-
"agent-turn",
|
|
136
|
-
"agent-turn",
|
|
137
|
-
"agent-turn",
|
|
138
|
-
"final"
|
|
139
|
-
]);
|
|
140
|
-
expect(result.trace.events).toEqual(streamedEvents);
|
|
141
|
-
expect(result.transcript).toEqual([
|
|
142
|
-
{
|
|
143
|
-
agentId: "agent-1",
|
|
144
|
-
role: "state-initializer",
|
|
145
|
-
input:
|
|
146
|
-
"Mission: Decide whether the shared protocol can support portable replay.\nShared turn 1: read the shared state and return an improved shared-state update.\n\nShared state:\n(empty)",
|
|
147
|
-
output: "state-initializer:agent-1 initialized the shared state."
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
agentId: "agent-2",
|
|
151
|
-
role: "state-reviewer",
|
|
152
|
-
input:
|
|
153
|
-
"Mission: Decide whether the shared protocol can support portable replay.\nShared turn 2: read the shared state and return an improved shared-state update.\n\nShared state:\n(empty)",
|
|
154
|
-
output: "state-reviewer:agent-2 initialized the shared state."
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
agentId: "agent-3",
|
|
158
|
-
role: "state-synthesizer",
|
|
159
|
-
input:
|
|
160
|
-
"Mission: Decide whether the shared protocol can support portable replay.\nShared turn 3: read the shared state and return an improved shared-state update.\n\nShared state:\n(empty)",
|
|
161
|
-
output: "state-synthesizer:agent-3 initialized the shared state."
|
|
162
|
-
}
|
|
163
|
-
]);
|
|
164
|
-
expect(result.trace.transcript).toEqual(result.transcript);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("gives every shared agent the same organizational memory snapshot", async () => {
|
|
168
|
-
const requests: ModelRequest[] = [];
|
|
169
|
-
const model: ConfiguredModelProvider = {
|
|
170
|
-
id: "shared-memory-snapshot-model",
|
|
171
|
-
async generate(request) {
|
|
172
|
-
requests.push(request);
|
|
173
|
-
return { text: `output-${String(request.metadata.agentId)}` };
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
await run({
|
|
178
|
-
intent: "Coordinate from historical organization memory.",
|
|
179
|
-
protocol: {
|
|
180
|
-
kind: "shared",
|
|
181
|
-
maxTurns: 2,
|
|
182
|
-
organizationalMemory: "Prior task memory: uploader and verifier roles were useful."
|
|
183
|
-
},
|
|
184
|
-
tier: "fast",
|
|
185
|
-
model,
|
|
186
|
-
agents: [
|
|
187
|
-
{ id: "agent-a", role: "autonomous-agent" },
|
|
188
|
-
{ id: "agent-b", role: "autonomous-agent" }
|
|
189
|
-
]
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
expect(requests).toHaveLength(2);
|
|
193
|
-
const userPrompts = requests.map((request) => request.messages.find((message) => message.role === "user")?.content);
|
|
194
|
-
expect(userPrompts).toEqual([
|
|
195
|
-
"Mission: Coordinate from historical organization memory.\nShared turn 1: read the shared state and return an improved shared-state update.\n\nShared state:\nPrior task memory: uploader and verifier roles were useful.",
|
|
196
|
-
"Mission: Coordinate from historical organization memory.\nShared turn 2: read the shared state and return an improved shared-state update.\n\nShared state:\nPrior task memory: uploader and verifier roles were useful."
|
|
197
|
-
]);
|
|
198
|
-
expect(userPrompts[1]).not.toContain("output-agent-a");
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it("threads runtime tool availability through every shared model turn", async () => {
|
|
202
|
-
interface LookupInput extends JsonObject {
|
|
203
|
-
readonly query: string;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
interface LookupOutput extends JsonObject {
|
|
207
|
-
readonly answer: string;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const requests: ModelRequest[] = [];
|
|
211
|
-
const lookupTool: RuntimeTool<LookupInput, LookupOutput> = {
|
|
212
|
-
identity: {
|
|
213
|
-
id: "fixture.lookup",
|
|
214
|
-
name: "lookup",
|
|
215
|
-
description: "Lookup contextual facts for the active mission."
|
|
216
|
-
},
|
|
217
|
-
inputSchema: {
|
|
218
|
-
kind: "json-schema",
|
|
219
|
-
schema: {
|
|
220
|
-
type: "object",
|
|
221
|
-
properties: {
|
|
222
|
-
query: { type: "string" }
|
|
223
|
-
},
|
|
224
|
-
required: ["query"],
|
|
225
|
-
additionalProperties: false
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
execute(input, context) {
|
|
229
|
-
return {
|
|
230
|
-
type: "success",
|
|
231
|
-
toolCallId: context.toolCallId,
|
|
232
|
-
tool: this.identity,
|
|
233
|
-
output: {
|
|
234
|
-
answer: `found:${input.query}`
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
const model: ConfiguredModelProvider = {
|
|
240
|
-
id: "shared-tool-availability-model",
|
|
241
|
-
async generate(request) {
|
|
242
|
-
requests.push(request);
|
|
243
|
-
return { text: `turn-${requests.length}` };
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
await run({
|
|
248
|
-
intent: "Use available tools while updating shared state.",
|
|
249
|
-
protocol: { kind: "shared", maxTurns: 2 },
|
|
250
|
-
tier: "fast",
|
|
251
|
-
model,
|
|
252
|
-
agents: [
|
|
253
|
-
{ id: "initializer-seat", role: "initializer" },
|
|
254
|
-
{ id: "reviewer-seat", role: "reviewer" }
|
|
255
|
-
],
|
|
256
|
-
tools: [lookupTool]
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
expect(requests).toHaveLength(2);
|
|
260
|
-
expect(requests.map((request) => request.metadata.tools)).toEqual([
|
|
261
|
-
runtimeToolManifest([lookupTool]),
|
|
262
|
-
runtimeToolManifest([lookupTool])
|
|
263
|
-
]);
|
|
264
|
-
});
|
|
265
|
-
});
|