@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,428 @@
1
+ import type {
2
+ AgentSpec,
3
+ BroadcastContribution,
4
+ BroadcastProtocolConfig,
5
+ ConfiguredModelProvider,
6
+ CostSummary,
7
+ DogpileOptions,
8
+ JsonObject,
9
+ JsonValue,
10
+ ModelRequest,
11
+ ModelResponse,
12
+ ReplayTraceProtocolDecision,
13
+ ReplayTraceProviderCall,
14
+ RuntimeTool,
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
+ } from "./defaults.js";
37
+ import { throwIfAborted } from "./cancellation.js";
38
+ import { parseAgentDecision } from "./decisions.js";
39
+ import { generateModelTurn } from "./model.js";
40
+ import { evaluateTerminationStop } from "./termination.js";
41
+ import { createRuntimeToolExecutor, executeModelResponseToolRequests, runtimeToolAvailability } from "./tools.js";
42
+
43
+ interface BroadcastRunOptions {
44
+ readonly intent: string;
45
+ readonly protocol: BroadcastProtocolConfig;
46
+ readonly tier: Tier;
47
+ readonly model: ConfiguredModelProvider;
48
+ readonly agents: readonly AgentSpec[];
49
+ readonly tools: readonly RuntimeTool<JsonObject, JsonValue>[];
50
+ readonly temperature: number;
51
+ readonly budget?: DogpileOptions["budget"];
52
+ readonly seed?: string | number;
53
+ readonly signal?: AbortSignal;
54
+ readonly terminate?: TerminationCondition;
55
+ readonly emit?: (event: RunEvent) => void;
56
+ }
57
+
58
+ export async function runBroadcast(options: BroadcastRunOptions): Promise<RunResult> {
59
+ const runId = createRunId();
60
+ const events: RunEvent[] = [];
61
+ const transcript: TranscriptEntry[] = [];
62
+ const protocolDecisions: ReplayTraceProtocolDecision[] = [];
63
+ const providerCalls: ReplayTraceProviderCall[] = [];
64
+ let totalCost = emptyCost();
65
+ const maxRounds = options.protocol.maxRounds ?? 2;
66
+ let firstRoundContributions: readonly BroadcastContribution[] = [];
67
+ let lastContributions: readonly BroadcastContribution[] = [];
68
+ const startedAtMs = nowMs();
69
+ let stopped = false;
70
+ let termination: TerminationStopRecord | undefined;
71
+
72
+ const emit = (event: RunEvent): void => {
73
+ events.push(event);
74
+ options.emit?.(event);
75
+ };
76
+
77
+ const recordProtocolDecision = (
78
+ event: RunEvent,
79
+ decisionOptions?: Parameters<typeof createReplayTraceProtocolDecision>[3]
80
+ ): void => {
81
+ protocolDecisions.push(
82
+ createReplayTraceProtocolDecision("broadcast", event, events.length - 1, decisionOptions)
83
+ );
84
+ };
85
+
86
+ const toolExecutor = createRuntimeToolExecutor({
87
+ runId,
88
+ protocol: "broadcast",
89
+ tier: options.tier,
90
+ tools: options.tools,
91
+ emit(event): void {
92
+ emit(event);
93
+ recordProtocolDecision(event);
94
+ },
95
+ getTrace: () => ({ events, transcript }),
96
+ ...(options.signal !== undefined ? { abortSignal: options.signal } : {})
97
+ });
98
+ const toolAvailability = runtimeToolAvailability(toolExecutor.tools);
99
+
100
+ throwIfAborted(options.signal, options.model.id);
101
+
102
+ for (const agent of options.agents) {
103
+ const event: RunEvent = {
104
+ type: "role-assignment",
105
+ runId,
106
+ at: new Date().toISOString(),
107
+ agentId: agent.id,
108
+ role: agent.role
109
+ };
110
+ emit(event);
111
+ recordProtocolDecision(event);
112
+ }
113
+
114
+ for (let round = 1; round <= maxRounds; round += 1) {
115
+ if (stopIfNeeded()) {
116
+ break;
117
+ }
118
+
119
+ const providerCallSlots: ReplayTraceProviderCall[] = [];
120
+ const turnResults = await Promise.all(
121
+ options.agents.map(async (agent, agentIndex) => {
122
+ const turn = transcript.length + agentIndex + 1;
123
+ const input = buildBroadcastInput(options.intent, round, maxRounds, firstRoundContributions);
124
+ const request: ModelRequest = {
125
+ temperature: options.temperature,
126
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
127
+ metadata: {
128
+ runId,
129
+ protocol: "broadcast",
130
+ agentId: agent.id,
131
+ role: agent.role,
132
+ tier: options.tier,
133
+ round,
134
+ ...toolAvailability
135
+ },
136
+ messages: [
137
+ {
138
+ role: "system",
139
+ content: buildSystemPrompt(agent)
140
+ },
141
+ {
142
+ role: "user",
143
+ content: input
144
+ }
145
+ ]
146
+ };
147
+ const response = await generateModelTurn({
148
+ model: options.model,
149
+ request,
150
+ runId,
151
+ agent,
152
+ input,
153
+ emit,
154
+ callId: providerCallIdFor(runId, providerCalls.length + agentIndex + 1),
155
+ onProviderCall(call): void {
156
+ providerCallSlots[agentIndex] = call;
157
+ }
158
+ });
159
+ const decision = parseAgentDecision(response.text);
160
+ const toolCalls = await executeModelResponseToolRequests({
161
+ response,
162
+ executor: toolExecutor,
163
+ agentId: agent.id,
164
+ role: agent.role,
165
+ turn,
166
+ metadata: {
167
+ round
168
+ }
169
+ });
170
+ throwIfAborted(options.signal, options.model.id);
171
+
172
+ return {
173
+ agent,
174
+ agentIndex,
175
+ turn,
176
+ input,
177
+ response,
178
+ decision,
179
+ toolCalls,
180
+ turnCost: responseCost(response)
181
+ };
182
+ })
183
+ );
184
+ providerCalls.push(...providerCallSlots.filter((call): call is ReplayTraceProviderCall => call !== undefined));
185
+
186
+ const contributions: BroadcastContribution[] = [];
187
+ for (const result of turnResults) {
188
+ totalCost = addCost(totalCost, result.turnCost);
189
+ transcript.push({
190
+ agentId: result.agent.id,
191
+ role: result.agent.role,
192
+ input: result.input,
193
+ output: result.response.text,
194
+ ...(result.decision !== undefined ? { decision: result.decision } : {}),
195
+ ...(result.toolCalls.length > 0 ? { toolCalls: result.toolCalls } : {})
196
+ });
197
+
198
+ contributions.push({
199
+ agentId: result.agent.id,
200
+ role: result.agent.role,
201
+ output: result.response.text,
202
+ ...(result.decision !== undefined ? { decision: result.decision } : {})
203
+ });
204
+
205
+ const event: RunEvent = {
206
+ type: "agent-turn",
207
+ runId,
208
+ at: new Date().toISOString(),
209
+ agentId: result.agent.id,
210
+ role: result.agent.role,
211
+ input: result.input,
212
+ output: result.response.text,
213
+ ...(result.decision !== undefined ? { decision: result.decision } : {}),
214
+ cost: totalCost
215
+ };
216
+ emit(event);
217
+ recordProtocolDecision(event, {
218
+ round,
219
+ turn: result.turn,
220
+ transcriptEntryCount: transcript.length,
221
+ contributionCount: result.agentIndex + 1
222
+ });
223
+ }
224
+
225
+ if (contributions.length === 0) {
226
+ break;
227
+ }
228
+ if (round === 1) {
229
+ firstRoundContributions = contributions;
230
+ }
231
+ lastContributions = contributions;
232
+
233
+ const broadcast: RunEvent = {
234
+ type: "broadcast",
235
+ runId,
236
+ at: new Date().toISOString(),
237
+ round,
238
+ contributions,
239
+ cost: totalCost
240
+ };
241
+ emit(broadcast);
242
+ recordProtocolDecision(broadcast, {
243
+ round,
244
+ transcriptEntryCount: transcript.length,
245
+ contributionCount: contributions.length
246
+ });
247
+
248
+ if (stopIfNeeded()) {
249
+ break;
250
+ }
251
+ }
252
+
253
+ const output = synthesizeBroadcastOutput(lastContributions);
254
+ throwIfAborted(options.signal, options.model.id);
255
+ const final: RunEvent = {
256
+ type: "final",
257
+ runId,
258
+ at: new Date().toISOString(),
259
+ output,
260
+ cost: totalCost,
261
+ transcript: createTranscriptLink(transcript),
262
+ ...(termination !== undefined ? { termination } : {})
263
+ };
264
+ emit(final);
265
+ recordProtocolDecision(final, {
266
+ transcriptEntryCount: transcript.length
267
+ });
268
+ const finalEvent = events.at(-1);
269
+
270
+ return {
271
+ output,
272
+ eventLog: createRunEventLog(runId, "broadcast", events),
273
+ trace: {
274
+ schemaVersion: "1.0",
275
+ runId,
276
+ protocol: "broadcast",
277
+ tier: options.tier,
278
+ modelProviderId: options.model.id,
279
+ agentsUsed: options.agents,
280
+ inputs: createReplayTraceRunInputs({
281
+ intent: options.intent,
282
+ protocol: options.protocol,
283
+ tier: options.tier,
284
+ modelProviderId: options.model.id,
285
+ agents: options.agents,
286
+ temperature: options.temperature
287
+ }),
288
+ budget: createReplayTraceBudget({
289
+ tier: options.tier,
290
+ ...(options.budget ? { caps: options.budget } : {}),
291
+ ...(options.terminate ? { termination: options.terminate } : {})
292
+ }),
293
+ budgetStateChanges: createReplayTraceBudgetStateChanges(events),
294
+ seed: createReplayTraceSeed(options.seed),
295
+ protocolDecisions,
296
+ providerCalls,
297
+ finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
298
+ type: "final",
299
+ runId,
300
+ at: "",
301
+ output,
302
+ cost: totalCost,
303
+ transcript: createTranscriptLink(transcript)
304
+ }),
305
+ events,
306
+ transcript
307
+ },
308
+ transcript,
309
+ usage: createRunUsage(totalCost),
310
+ metadata: createRunMetadata({
311
+ runId,
312
+ protocol: "broadcast",
313
+ tier: options.tier,
314
+ modelProviderId: options.model.id,
315
+ agentsUsed: options.agents,
316
+ events
317
+ }),
318
+ accounting: createRunAccounting({
319
+ tier: options.tier,
320
+ ...(options.budget ? { budget: options.budget } : {}),
321
+ ...(options.terminate ? { termination: options.terminate } : {}),
322
+ cost: totalCost,
323
+ events
324
+ }),
325
+ cost: totalCost
326
+ };
327
+
328
+ function stopIfNeeded(): boolean {
329
+ throwIfAborted(options.signal, options.model.id);
330
+
331
+ if (stopped || !options.terminate) {
332
+ return stopped;
333
+ }
334
+
335
+ const stopRecord = evaluateTerminationStop(options.terminate, {
336
+ runId,
337
+ protocol: "broadcast",
338
+ tier: options.tier,
339
+ cost: totalCost,
340
+ events,
341
+ transcript,
342
+ iteration: transcript.length,
343
+ elapsedMs: elapsedMs(startedAtMs)
344
+ });
345
+
346
+ if (!stopRecord) {
347
+ return false;
348
+ }
349
+
350
+ stopped = true;
351
+ termination = stopRecord;
352
+ if (stopRecord.reason === "budget") {
353
+ emitBudgetStop(stopRecord);
354
+ }
355
+ return true;
356
+ }
357
+
358
+ function emitBudgetStop(record: TerminationStopRecord): void {
359
+ const event: RunEvent = {
360
+ type: "budget-stop",
361
+ runId,
362
+ at: new Date().toISOString(),
363
+ reason: record.budgetReason ?? "cost",
364
+ cost: totalCost,
365
+ iteration: transcript.length,
366
+ elapsedMs: elapsedMs(startedAtMs),
367
+ detail: record.detail ?? {}
368
+ };
369
+ emit(event);
370
+ recordProtocolDecision(event, {
371
+ transcriptEntryCount: transcript.length
372
+ });
373
+ }
374
+ }
375
+
376
+ function buildSystemPrompt(agent: AgentSpec): string {
377
+ const instruction = agent.instructions ? `\nInstructions: ${agent.instructions}` : "";
378
+ return `You are ${agent.id}, acting as ${agent.role} in a Broadcast multi-agent protocol.${instruction}`;
379
+ }
380
+
381
+ function buildBroadcastInput(
382
+ intent: string,
383
+ round: number,
384
+ maxRounds: number,
385
+ firstRoundContributions: readonly BroadcastContribution[]
386
+ ): string {
387
+ if (maxRounds === 1) {
388
+ return `Mission: ${intent}\nBroadcast round ${round}: contribute independently before synthesis.`;
389
+ }
390
+ if (round === 1) {
391
+ return `Mission: ${intent}\nBroadcast round 1: broadcast your intended role and participation decision. Do not produce the final plan yet.`;
392
+ }
393
+
394
+ const intentions = firstRoundContributions
395
+ .map((contribution) => `${contribution.role}:${contribution.agentId} => ${contribution.output}`)
396
+ .join("\n");
397
+ return `Mission: ${intent}\n\nRound 1 intentions:\n${intentions || "(none)"}\n\nBroadcast round ${round}: make your final contribution or abstention decision informed by all round 1 intentions.`;
398
+ }
399
+
400
+ function synthesizeBroadcastOutput(contributions: readonly BroadcastContribution[]): string {
401
+ return contributions.map((entry) => `${entry.role}:${entry.agentId} => ${entry.output}`).join("\n");
402
+ }
403
+
404
+ function responseCost(response: ModelResponse): CostSummary {
405
+ return {
406
+ usd: response.costUsd ?? 0,
407
+ inputTokens: response.usage?.inputTokens ?? 0,
408
+ outputTokens: response.usage?.outputTokens ?? 0,
409
+ totalTokens: response.usage?.totalTokens ?? 0
410
+ };
411
+ }
412
+
413
+ function createRunId(): string {
414
+ const random = globalThis.crypto?.randomUUID?.();
415
+ return random ?? `run-${Date.now().toString(36)}`;
416
+ }
417
+
418
+ function nowMs(): number {
419
+ return globalThis.performance?.now() ?? Date.now();
420
+ }
421
+
422
+ function elapsedMs(startedAtMs: number): number {
423
+ return Math.max(0, nowMs() - startedAtMs);
424
+ }
425
+
426
+ function providerCallIdFor(runId: string, oneBasedIndex: number): string {
427
+ return `${runId}:provider-call:${oneBasedIndex}`;
428
+ }
@@ -0,0 +1,40 @@
1
+ import { DogpileError, type JsonObject } from "../types.js";
2
+
3
+ export function throwIfAborted(signal: AbortSignal | undefined, providerId: string): void {
4
+ if (!signal?.aborted) {
5
+ return;
6
+ }
7
+
8
+ throw createAbortErrorFromSignal(signal, providerId);
9
+ }
10
+
11
+ export function createAbortError(providerId: string, detail?: JsonObject, cause?: unknown): DogpileError {
12
+ return new DogpileError({
13
+ code: "aborted",
14
+ message: "The operation was aborted.",
15
+ retryable: false,
16
+ providerId,
17
+ ...(detail !== undefined ? { detail } : {}),
18
+ ...(cause !== undefined ? { cause } : {})
19
+ });
20
+ }
21
+
22
+ export function createAbortErrorFromSignal(signal: AbortSignal, providerId: string): DogpileError {
23
+ if (DogpileError.isInstance(signal.reason)) {
24
+ return signal.reason;
25
+ }
26
+
27
+ return createAbortError(providerId, undefined, signal.reason);
28
+ }
29
+
30
+ export function createTimeoutError(providerId: string, timeoutMs: number): DogpileError {
31
+ return new DogpileError({
32
+ code: "timeout",
33
+ message: `The operation timed out after ${timeoutMs}ms.`,
34
+ retryable: true,
35
+ providerId,
36
+ detail: {
37
+ timeoutMs
38
+ }
39
+ });
40
+ }