@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.
Files changed (88) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +16 -0
  3. package/README.md +842 -0
  4. package/dist/browser/index.d.ts +8 -0
  5. package/dist/browser/index.d.ts.map +1 -0
  6. package/dist/browser/index.js +4493 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/index.d.ts +17 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +14 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/providers/openai-compatible.d.ts +44 -0
  13. package/dist/providers/openai-compatible.d.ts.map +1 -0
  14. package/dist/providers/openai-compatible.js +305 -0
  15. package/dist/providers/openai-compatible.js.map +1 -0
  16. package/dist/runtime/broadcast.d.ts +18 -0
  17. package/dist/runtime/broadcast.d.ts.map +1 -0
  18. package/dist/runtime/broadcast.js +335 -0
  19. package/dist/runtime/broadcast.js.map +1 -0
  20. package/dist/runtime/cancellation.d.ts +6 -0
  21. package/dist/runtime/cancellation.d.ts.map +1 -0
  22. package/dist/runtime/cancellation.js +35 -0
  23. package/dist/runtime/cancellation.js.map +1 -0
  24. package/dist/runtime/coordinator.d.ts +18 -0
  25. package/dist/runtime/coordinator.d.ts.map +1 -0
  26. package/dist/runtime/coordinator.js +434 -0
  27. package/dist/runtime/coordinator.js.map +1 -0
  28. package/dist/runtime/decisions.d.ts +5 -0
  29. package/dist/runtime/decisions.d.ts.map +1 -0
  30. package/dist/runtime/decisions.js +31 -0
  31. package/dist/runtime/decisions.js.map +1 -0
  32. package/dist/runtime/defaults.d.ts +63 -0
  33. package/dist/runtime/defaults.d.ts.map +1 -0
  34. package/dist/runtime/defaults.js +426 -0
  35. package/dist/runtime/defaults.js.map +1 -0
  36. package/dist/runtime/engine.d.ts +79 -0
  37. package/dist/runtime/engine.d.ts.map +1 -0
  38. package/dist/runtime/engine.js +723 -0
  39. package/dist/runtime/engine.js.map +1 -0
  40. package/dist/runtime/model.d.ts +14 -0
  41. package/dist/runtime/model.d.ts.map +1 -0
  42. package/dist/runtime/model.js +82 -0
  43. package/dist/runtime/model.js.map +1 -0
  44. package/dist/runtime/sequential.d.ts +18 -0
  45. package/dist/runtime/sequential.d.ts.map +1 -0
  46. package/dist/runtime/sequential.js +277 -0
  47. package/dist/runtime/sequential.js.map +1 -0
  48. package/dist/runtime/shared.d.ts +18 -0
  49. package/dist/runtime/shared.d.ts.map +1 -0
  50. package/dist/runtime/shared.js +288 -0
  51. package/dist/runtime/shared.js.map +1 -0
  52. package/dist/runtime/termination.d.ts +77 -0
  53. package/dist/runtime/termination.d.ts.map +1 -0
  54. package/dist/runtime/termination.js +355 -0
  55. package/dist/runtime/termination.js.map +1 -0
  56. package/dist/runtime/tools.d.ts +314 -0
  57. package/dist/runtime/tools.d.ts.map +1 -0
  58. package/dist/runtime/tools.js +969 -0
  59. package/dist/runtime/tools.js.map +1 -0
  60. package/dist/runtime/validation.d.ts +23 -0
  61. package/dist/runtime/validation.d.ts.map +1 -0
  62. package/dist/runtime/validation.js +656 -0
  63. package/dist/runtime/validation.js.map +1 -0
  64. package/dist/types.d.ts +2434 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +81 -0
  67. package/dist/types.js.map +1 -0
  68. package/package.json +157 -0
  69. package/src/browser/index.ts +7 -0
  70. package/src/index.ts +195 -0
  71. package/src/providers/openai-compatible.ts +406 -0
  72. package/src/runtime/broadcast.test.ts +355 -0
  73. package/src/runtime/broadcast.ts +428 -0
  74. package/src/runtime/cancellation.ts +40 -0
  75. package/src/runtime/coordinator.test.ts +468 -0
  76. package/src/runtime/coordinator.ts +581 -0
  77. package/src/runtime/decisions.ts +38 -0
  78. package/src/runtime/defaults.ts +547 -0
  79. package/src/runtime/engine.ts +880 -0
  80. package/src/runtime/model.ts +117 -0
  81. package/src/runtime/sequential.test.ts +262 -0
  82. package/src/runtime/sequential.ts +357 -0
  83. package/src/runtime/shared.test.ts +265 -0
  84. package/src/runtime/shared.ts +367 -0
  85. package/src/runtime/termination.ts +463 -0
  86. package/src/runtime/tools.ts +1518 -0
  87. package/src/runtime/validation.ts +771 -0
  88. package/src/types.ts +2729 -0
@@ -0,0 +1,117 @@
1
+ import type {
2
+ AgentSpec,
3
+ ConfiguredModelProvider,
4
+ ModelRequest,
5
+ ModelResponse,
6
+ RuntimeToolExecutionRequest,
7
+ ReplayTraceProviderCall,
8
+ RunEvent
9
+ } from "../types.js";
10
+ import { throwIfAborted } from "./cancellation.js";
11
+
12
+ interface GenerateModelTurnOptions {
13
+ readonly model: ConfiguredModelProvider;
14
+ readonly request: ModelRequest;
15
+ readonly runId: string;
16
+ readonly agent: AgentSpec;
17
+ readonly input: string;
18
+ readonly emit: (event: RunEvent) => void;
19
+ readonly callId: string;
20
+ readonly onProviderCall?: (call: ReplayTraceProviderCall) => void;
21
+ }
22
+
23
+ type ModelUsage = NonNullable<ModelResponse["usage"]>;
24
+
25
+ export async function generateModelTurn(options: GenerateModelTurnOptions): Promise<ModelResponse> {
26
+ const startedAt = new Date().toISOString();
27
+ let response: ModelResponse;
28
+
29
+ throwIfAborted(options.request.signal, options.model.id);
30
+
31
+ if (!options.model.stream) {
32
+ response = await options.model.generate(options.request);
33
+ throwIfAborted(options.request.signal, options.model.id);
34
+ recordProviderCall(response, startedAt, options);
35
+ return response;
36
+ }
37
+
38
+ let text = "";
39
+ let chunkIndex = 0;
40
+ let usage: ModelUsage | undefined;
41
+ let costUsd: number | undefined;
42
+ let finishReason: ModelResponse["finishReason"] | undefined;
43
+ let toolRequests: readonly RuntimeToolExecutionRequest[] | undefined;
44
+ let metadata: ModelResponse["metadata"] | undefined;
45
+
46
+ for await (const chunk of options.model.stream(options.request)) {
47
+ throwIfAborted(options.request.signal, options.model.id);
48
+ text += chunk.text;
49
+
50
+ options.emit({
51
+ type: "model-output-chunk",
52
+ runId: options.runId,
53
+ at: new Date().toISOString(),
54
+ agentId: options.agent.id,
55
+ role: options.agent.role,
56
+ input: options.input,
57
+ chunkIndex,
58
+ text: chunk.text,
59
+ output: text
60
+ });
61
+ chunkIndex += 1;
62
+
63
+ if (chunk.usage) {
64
+ usage = chunk.usage;
65
+ }
66
+ if (chunk.costUsd !== undefined) {
67
+ costUsd = chunk.costUsd;
68
+ }
69
+ if (chunk.finishReason !== undefined) {
70
+ finishReason = chunk.finishReason;
71
+ }
72
+ if (chunk.toolRequests !== undefined) {
73
+ toolRequests = chunk.toolRequests;
74
+ }
75
+ if (chunk.metadata !== undefined) {
76
+ metadata = chunk.metadata;
77
+ }
78
+ }
79
+
80
+ response = {
81
+ text,
82
+ ...(finishReason !== undefined ? { finishReason } : {}),
83
+ ...(toolRequests && toolRequests.length > 0 ? { toolRequests } : {}),
84
+ ...(usage ? { usage } : {}),
85
+ ...(costUsd !== undefined ? { costUsd } : {}),
86
+ ...(metadata !== undefined ? { metadata } : {})
87
+ };
88
+ throwIfAborted(options.request.signal, options.model.id);
89
+ recordProviderCall(response, startedAt, options);
90
+ return response;
91
+ }
92
+
93
+ function recordProviderCall(
94
+ response: ModelResponse,
95
+ startedAt: string,
96
+ options: GenerateModelTurnOptions
97
+ ): void {
98
+ options.onProviderCall?.({
99
+ kind: "replay-trace-provider-call",
100
+ callId: options.callId,
101
+ providerId: options.model.id,
102
+ startedAt,
103
+ completedAt: new Date().toISOString(),
104
+ agentId: options.agent.id,
105
+ role: options.agent.role,
106
+ request: requestForTrace(options.request),
107
+ response
108
+ });
109
+ }
110
+
111
+ function requestForTrace(request: ModelRequest): ModelRequest {
112
+ return {
113
+ messages: request.messages,
114
+ temperature: request.temperature,
115
+ metadata: request.metadata
116
+ };
117
+ }
@@ -0,0 +1,262 @@
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
+ });