@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,547 @@
1
+ import type {
2
+ AgentSpec,
3
+ Budget,
4
+ CostSummary,
5
+ Protocol,
6
+ ProtocolConfig,
7
+ ReplayTraceBudget,
8
+ ReplayTraceFinalOutput,
9
+ ReplayTraceProtocolDecision,
10
+ ReplayTraceProtocolDecisionType,
11
+ ReplayTraceProviderCall,
12
+ ReplayTraceRunInputs,
13
+ ReplayTraceBudgetStateChange,
14
+ ReplayTraceSeed,
15
+ RunResult,
16
+ RunAccounting,
17
+ RunEvent,
18
+ RunEventLog,
19
+ RunMetadata,
20
+ RunUsage,
21
+ Tier,
22
+ TranscriptEntry,
23
+ TranscriptLink
24
+ } from "../types.js";
25
+
26
+ type SerializableRecord = Record<string, unknown>;
27
+
28
+ export function normalizeProtocol(protocol: Protocol | ProtocolConfig): ProtocolConfig {
29
+ if (typeof protocol !== "string") {
30
+ return protocol;
31
+ }
32
+
33
+ switch (protocol) {
34
+ case "sequential":
35
+ return { kind: "sequential", maxTurns: 3 };
36
+ case "coordinator":
37
+ return { kind: "coordinator", maxTurns: 3 };
38
+ case "broadcast":
39
+ return { kind: "broadcast", maxRounds: 2 };
40
+ case "shared":
41
+ return { kind: "shared", maxTurns: 3 };
42
+ }
43
+ }
44
+
45
+ export function defaultAgents(): readonly AgentSpec[] {
46
+ return [
47
+ { id: "agent-1", role: "planner", instructions: "Frame the mission and identify the important constraints." },
48
+ { id: "agent-2", role: "critic", instructions: "Stress-test the previous contribution and improve weak spots." },
49
+ { id: "agent-3", role: "synthesizer", instructions: "Produce the final useful answer from the accumulated work." }
50
+ ];
51
+ }
52
+
53
+ export function orderAgentsForTemperature(
54
+ agents: readonly AgentSpec[],
55
+ temperature: number,
56
+ seed?: string | number
57
+ ): readonly AgentSpec[] {
58
+ if (temperature !== 0) {
59
+ return agents;
60
+ }
61
+
62
+ if (seed !== undefined) {
63
+ return [...agents].sort((left, right) => compareAgentsBySeededSelection(left, right, seed));
64
+ }
65
+
66
+ return [...agents].sort(compareAgentsByStableIdentity);
67
+ }
68
+
69
+ function compareAgentsBySeededSelection(left: AgentSpec, right: AgentSpec, seed: string | number): number {
70
+ const leftScore = deterministicSelectionScore(seed, left);
71
+ const rightScore = deterministicSelectionScore(seed, right);
72
+ if (leftScore !== rightScore) {
73
+ return leftScore - rightScore;
74
+ }
75
+
76
+ return compareAgentsByStableIdentity(left, right);
77
+ }
78
+
79
+ function deterministicSelectionScore(seed: string | number, agent: AgentSpec): number {
80
+ return stableHash(`${String(seed)}\u0000${agent.id}\u0000${agent.role}\u0000${agent.instructions ?? ""}`);
81
+ }
82
+
83
+ function stableHash(input: string): number {
84
+ let hash = 0x811c9dc5;
85
+
86
+ for (let index = 0; index < input.length; index += 1) {
87
+ hash ^= input.charCodeAt(index);
88
+ hash = Math.imul(hash, 0x01000193);
89
+ }
90
+
91
+ return hash >>> 0;
92
+ }
93
+
94
+ function compareAgentsByStableIdentity(left: AgentSpec, right: AgentSpec): number {
95
+ const idOrder = left.id.localeCompare(right.id);
96
+ if (idOrder !== 0) {
97
+ return idOrder;
98
+ }
99
+
100
+ const roleOrder = left.role.localeCompare(right.role);
101
+ if (roleOrder !== 0) {
102
+ return roleOrder;
103
+ }
104
+
105
+ return (left.instructions ?? "").localeCompare(right.instructions ?? "");
106
+ }
107
+
108
+ export function tierTemperature(tier: Tier): number {
109
+ switch (tier) {
110
+ case "fast":
111
+ return 0;
112
+ case "balanced":
113
+ return 0.2;
114
+ case "quality":
115
+ return 0.4;
116
+ }
117
+ }
118
+
119
+ export function emptyCost(): CostSummary {
120
+ return { usd: 0, inputTokens: 0, outputTokens: 0, totalTokens: 0 };
121
+ }
122
+
123
+ export function addCost(left: CostSummary, right: CostSummary): CostSummary {
124
+ return {
125
+ usd: left.usd + right.usd,
126
+ inputTokens: left.inputTokens + right.inputTokens,
127
+ outputTokens: left.outputTokens + right.outputTokens,
128
+ totalTokens: left.totalTokens + right.totalTokens
129
+ };
130
+ }
131
+
132
+ export function createTranscriptLink(transcript: readonly TranscriptEntry[]): TranscriptLink {
133
+ return {
134
+ kind: "trace-transcript",
135
+ entryCount: transcript.length,
136
+ lastEntryIndex: transcript.length === 0 ? null : transcript.length - 1
137
+ };
138
+ }
139
+
140
+ export function createRunEventLog(runId: string, protocol: Protocol, events: readonly RunEvent[]): RunEventLog {
141
+ return {
142
+ kind: "run-event-log",
143
+ runId,
144
+ protocol,
145
+ eventTypes: events.map((event) => event.type),
146
+ eventCount: events.length,
147
+ events
148
+ };
149
+ }
150
+
151
+ export function createRunUsage(cost: CostSummary): RunUsage {
152
+ return {
153
+ usd: cost.usd,
154
+ inputTokens: cost.inputTokens,
155
+ outputTokens: cost.outputTokens,
156
+ totalTokens: cost.totalTokens
157
+ };
158
+ }
159
+
160
+ export function createRunAccounting(options: {
161
+ readonly tier: Tier;
162
+ readonly budget?: Omit<Budget, "tier">;
163
+ readonly termination?: ReplayTraceBudget["termination"];
164
+ readonly cost: CostSummary;
165
+ readonly events: readonly RunEvent[];
166
+ }): RunAccounting {
167
+ const usage = createRunUsage(options.cost);
168
+ return {
169
+ kind: "run-accounting",
170
+ tier: options.tier,
171
+ ...(options.budget ? { budget: options.budget } : {}),
172
+ ...(options.termination ? { termination: options.termination } : {}),
173
+ usage,
174
+ cost: options.cost,
175
+ budgetStateChanges: createReplayTraceBudgetStateChanges(options.events),
176
+ ...(options.budget?.maxUsd !== undefined
177
+ ? { usdCapUtilization: options.budget.maxUsd === 0 ? 0 : options.cost.usd / options.budget.maxUsd }
178
+ : {}),
179
+ ...(options.budget?.maxTokens !== undefined
180
+ ? {
181
+ totalTokenCapUtilization:
182
+ options.budget.maxTokens === 0 ? 0 : options.cost.totalTokens / options.budget.maxTokens
183
+ }
184
+ : {})
185
+ };
186
+ }
187
+
188
+ export function createRunMetadata(options: {
189
+ readonly runId: string;
190
+ readonly protocol: Protocol;
191
+ readonly tier: Tier;
192
+ readonly modelProviderId: string;
193
+ readonly agentsUsed: readonly AgentSpec[];
194
+ readonly events: readonly RunEvent[];
195
+ }): RunMetadata {
196
+ const firstEvent = options.events[0];
197
+ const lastEvent = options.events.at(-1);
198
+ return {
199
+ runId: options.runId,
200
+ protocol: options.protocol,
201
+ tier: options.tier,
202
+ modelProviderId: options.modelProviderId,
203
+ agentsUsed: options.agentsUsed,
204
+ startedAt: firstEvent?.at ?? "",
205
+ completedAt: lastEvent?.at ?? ""
206
+ };
207
+ }
208
+
209
+ export function createReplayTraceRunInputs(options: {
210
+ readonly intent: string;
211
+ readonly protocol: ProtocolConfig;
212
+ readonly tier: Tier;
213
+ readonly modelProviderId: string;
214
+ readonly agents: readonly AgentSpec[];
215
+ readonly temperature: number;
216
+ }): ReplayTraceRunInputs {
217
+ return {
218
+ kind: "replay-trace-run-inputs",
219
+ intent: options.intent,
220
+ protocol: options.protocol,
221
+ tier: options.tier,
222
+ modelProviderId: options.modelProviderId,
223
+ agents: options.agents,
224
+ temperature: options.temperature
225
+ };
226
+ }
227
+
228
+ export function createReplayTraceBudget(options: {
229
+ readonly tier: Tier;
230
+ readonly caps?: Omit<Budget, "tier">;
231
+ readonly termination?: ReplayTraceBudget["termination"];
232
+ }): ReplayTraceBudget {
233
+ return {
234
+ kind: "replay-trace-budget",
235
+ tier: options.tier,
236
+ ...(options.caps ? { caps: options.caps } : {}),
237
+ ...(options.termination ? { termination: options.termination } : {})
238
+ };
239
+ }
240
+
241
+ export function createReplayTraceBudgetStateChanges(
242
+ events: readonly RunEvent[]
243
+ ): readonly ReplayTraceBudgetStateChange[] {
244
+ return events.flatMap((event, eventIndex): ReplayTraceBudgetStateChange[] => {
245
+ switch (event.type) {
246
+ case "agent-turn":
247
+ case "broadcast":
248
+ case "final":
249
+ return [
250
+ {
251
+ kind: "replay-trace-budget-state-change",
252
+ eventIndex,
253
+ eventType: event.type,
254
+ at: event.at,
255
+ cost: event.cost
256
+ }
257
+ ];
258
+ case "budget-stop":
259
+ return [
260
+ {
261
+ kind: "replay-trace-budget-state-change",
262
+ eventIndex,
263
+ eventType: event.type,
264
+ at: event.at,
265
+ cost: event.cost,
266
+ iteration: event.iteration,
267
+ elapsedMs: event.elapsedMs,
268
+ budgetReason: event.reason
269
+ }
270
+ ];
271
+ case "role-assignment":
272
+ case "model-request":
273
+ case "model-response":
274
+ case "model-output-chunk":
275
+ case "tool-call":
276
+ case "tool-result":
277
+ return [];
278
+ }
279
+ });
280
+ }
281
+
282
+ export function createReplayTraceSeed(seed: string | number | undefined): ReplayTraceSeed {
283
+ if (seed === undefined) {
284
+ return {
285
+ kind: "replay-trace-seed",
286
+ source: "none",
287
+ value: null
288
+ };
289
+ }
290
+
291
+ return {
292
+ kind: "replay-trace-seed",
293
+ source: "caller",
294
+ value: seed
295
+ };
296
+ }
297
+
298
+ export function createReplayTraceProtocolDecisions(
299
+ protocol: Protocol,
300
+ events: readonly RunEvent[]
301
+ ): readonly ReplayTraceProtocolDecision[] {
302
+ return events.map((event, eventIndex): ReplayTraceProtocolDecision => {
303
+ return createReplayTraceProtocolDecision(protocol, event, eventIndex);
304
+ });
305
+ }
306
+
307
+ export function createReplayTraceProtocolDecision(
308
+ protocol: Protocol,
309
+ event: RunEvent,
310
+ eventIndex: number,
311
+ options: {
312
+ readonly decision?: ReplayTraceProtocolDecisionType;
313
+ readonly turn?: number;
314
+ readonly phase?: ReplayTraceProtocolDecision["phase"];
315
+ readonly round?: number;
316
+ readonly transcriptEntryCount?: number;
317
+ readonly contributionCount?: number;
318
+ } = {}
319
+ ): ReplayTraceProtocolDecision {
320
+ const base = {
321
+ kind: "replay-trace-protocol-decision" as const,
322
+ eventIndex,
323
+ eventType: event.type,
324
+ protocol,
325
+ decision: options.decision ?? defaultProtocolDecision(event),
326
+ at: event.at,
327
+ ...(options.turn !== undefined ? { turn: options.turn } : {}),
328
+ ...(options.phase !== undefined ? { phase: options.phase } : {}),
329
+ ...(options.round !== undefined ? { round: options.round } : {}),
330
+ ...(options.transcriptEntryCount !== undefined ? { transcriptEntryCount: options.transcriptEntryCount } : {}),
331
+ ...(options.contributionCount !== undefined ? { contributionCount: options.contributionCount } : {})
332
+ };
333
+
334
+ switch (event.type) {
335
+ case "role-assignment":
336
+ return {
337
+ ...base,
338
+ agentId: event.agentId,
339
+ role: event.role
340
+ };
341
+ case "model-request":
342
+ return {
343
+ ...base,
344
+ agentId: event.agentId,
345
+ role: event.role,
346
+ callId: event.callId,
347
+ providerId: event.providerId,
348
+ input: event.request.messages.map((message) => message.content).join("\n")
349
+ };
350
+ case "model-response":
351
+ return {
352
+ ...base,
353
+ agentId: event.agentId,
354
+ role: event.role,
355
+ callId: event.callId,
356
+ providerId: event.providerId,
357
+ output: event.response.text
358
+ };
359
+ case "model-output-chunk":
360
+ return {
361
+ ...base,
362
+ agentId: event.agentId,
363
+ role: event.role,
364
+ input: event.input,
365
+ output: event.output
366
+ };
367
+ case "tool-call":
368
+ return {
369
+ ...base,
370
+ toolCallId: event.toolCallId,
371
+ tool: event.tool,
372
+ input: stableJsonStringify(event.input),
373
+ ...eventAgentScope(event)
374
+ };
375
+ case "tool-result":
376
+ return {
377
+ ...base,
378
+ toolCallId: event.toolCallId,
379
+ tool: event.tool,
380
+ output: stableJsonStringify(event.result),
381
+ ...eventAgentScope(event)
382
+ };
383
+ case "agent-turn":
384
+ return {
385
+ ...base,
386
+ agentId: event.agentId,
387
+ role: event.role,
388
+ input: event.input,
389
+ output: event.output,
390
+ cost: event.cost
391
+ };
392
+ case "broadcast":
393
+ return {
394
+ ...base,
395
+ round: event.round,
396
+ contributionCount: options.contributionCount ?? event.contributions.length,
397
+ cost: event.cost
398
+ };
399
+ case "budget-stop":
400
+ return {
401
+ ...base,
402
+ cost: event.cost,
403
+ budgetReason: event.reason
404
+ };
405
+ case "final":
406
+ return {
407
+ ...base,
408
+ output: event.output,
409
+ cost: event.cost
410
+ };
411
+ }
412
+ }
413
+
414
+ function defaultProtocolDecision(event: RunEvent): ReplayTraceProtocolDecisionType {
415
+ switch (event.type) {
416
+ case "role-assignment":
417
+ return "assign-role";
418
+ case "model-request":
419
+ return "start-model-call";
420
+ case "model-response":
421
+ return "complete-model-call";
422
+ case "model-output-chunk":
423
+ return "observe-model-output";
424
+ case "tool-call":
425
+ return "start-tool-call";
426
+ case "tool-result":
427
+ return "complete-tool-call";
428
+ case "agent-turn":
429
+ return "select-agent-turn";
430
+ case "broadcast":
431
+ return "collect-broadcast-round";
432
+ case "budget-stop":
433
+ return "stop-for-budget";
434
+ case "final":
435
+ return "finalize-output";
436
+ }
437
+ }
438
+
439
+ function eventAgentScope(event: {
440
+ readonly agentId?: string;
441
+ readonly role?: string;
442
+ }): Pick<ReplayTraceProtocolDecision, "agentId" | "role"> {
443
+ return {
444
+ ...(event.agentId !== undefined ? { agentId: event.agentId } : {}),
445
+ ...(event.role !== undefined ? { role: event.role } : {})
446
+ };
447
+ }
448
+
449
+ export function createReplayTraceFinalOutput(output: string, event: RunEvent): ReplayTraceFinalOutput {
450
+ if (event.type === "final") {
451
+ return {
452
+ kind: "replay-trace-final-output",
453
+ output,
454
+ cost: event.cost,
455
+ completedAt: event.at,
456
+ transcript: event.transcript
457
+ };
458
+ }
459
+
460
+ return {
461
+ kind: "replay-trace-final-output",
462
+ output,
463
+ cost: emptyCost(),
464
+ completedAt: event.at,
465
+ transcript: {
466
+ kind: "trace-transcript",
467
+ entryCount: 0,
468
+ lastEntryIndex: null
469
+ }
470
+ };
471
+ }
472
+
473
+ export function nextProviderCallId(
474
+ runId: string,
475
+ providerCalls: readonly ReplayTraceProviderCall[]
476
+ ): string {
477
+ return `${runId}:provider-call:${providerCalls.length + 1}`;
478
+ }
479
+
480
+ /**
481
+ * Normalize completed run artifacts into deterministic JSON shapes.
482
+ *
483
+ * This keeps caller-owned persistence stable across runtimes: object keys are
484
+ * sorted recursively, undefined object fields are omitted, non-finite numbers
485
+ * become JSON `null`, and negative zero is normalized to zero before callers
486
+ * serialize the returned result, trace, event log, transcript, or metadata.
487
+ */
488
+ export function canonicalizeRunResult(result: RunResult): RunResult {
489
+ const trace = canonicalizeSerializable(result.trace);
490
+ const eventLog: RunEventLog = {
491
+ eventCount: trace.events.length,
492
+ eventTypes: trace.events.map((event) => event.type),
493
+ events: trace.events,
494
+ kind: "run-event-log",
495
+ protocol: trace.protocol,
496
+ runId: trace.runId
497
+ };
498
+ const canonicalResult = {
499
+ accounting: canonicalizeSerializable(result.accounting),
500
+ cost: canonicalizeSerializable(result.cost),
501
+ ...(result.evaluation !== undefined ? { evaluation: canonicalizeSerializable(result.evaluation) } : {}),
502
+ eventLog,
503
+ metadata: canonicalizeSerializable(result.metadata),
504
+ output: result.output,
505
+ ...(result.quality !== undefined ? { quality: canonicalizeSerializable(result.quality) } : {}),
506
+ trace,
507
+ transcript: trace.transcript,
508
+ usage: canonicalizeSerializable(result.usage)
509
+ };
510
+
511
+ return canonicalResult;
512
+ }
513
+
514
+ export function stableJsonStringify(value: unknown): string {
515
+ return JSON.stringify(canonicalizeSerializable(value));
516
+ }
517
+
518
+ export function canonicalizeSerializable<T>(value: T): T {
519
+ if (Array.isArray(value)) {
520
+ return value.map((item) => canonicalizeSerializable(item)) as T;
521
+ }
522
+
523
+ if (typeof value === "number") {
524
+ if (Object.is(value, -0)) {
525
+ return 0 as T;
526
+ }
527
+ if (!Number.isFinite(value)) {
528
+ return null as T;
529
+ }
530
+ return value;
531
+ }
532
+
533
+ if (value === null || typeof value !== "object") {
534
+ return value;
535
+ }
536
+
537
+ const input = value as SerializableRecord;
538
+ const output: SerializableRecord = {};
539
+ for (const key of Object.keys(input).sort()) {
540
+ const child = input[key];
541
+ if (child !== undefined) {
542
+ output[key] = canonicalizeSerializable(child);
543
+ }
544
+ }
545
+
546
+ return output as T;
547
+ }