@dogpile/sdk 0.2.2 → 0.3.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/browser/index.js +1044 -507
  3. package/dist/browser/index.js.map +1 -1
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/runtime/broadcast.d.ts +1 -0
  9. package/dist/runtime/broadcast.d.ts.map +1 -1
  10. package/dist/runtime/broadcast.js +28 -19
  11. package/dist/runtime/broadcast.js.map +1 -1
  12. package/dist/runtime/coordinator.d.ts +1 -0
  13. package/dist/runtime/coordinator.d.ts.map +1 -1
  14. package/dist/runtime/coordinator.js +46 -21
  15. package/dist/runtime/coordinator.js.map +1 -1
  16. package/dist/runtime/engine.d.ts.map +1 -1
  17. package/dist/runtime/engine.js +5 -0
  18. package/dist/runtime/engine.js.map +1 -1
  19. package/dist/runtime/ids.d.ts +19 -0
  20. package/dist/runtime/ids.d.ts.map +1 -0
  21. package/dist/runtime/ids.js +36 -0
  22. package/dist/runtime/ids.js.map +1 -0
  23. package/dist/runtime/logger.d.ts +61 -0
  24. package/dist/runtime/logger.d.ts.map +1 -0
  25. package/dist/runtime/logger.js +114 -0
  26. package/dist/runtime/logger.js.map +1 -0
  27. package/dist/runtime/retry.d.ts +99 -0
  28. package/dist/runtime/retry.d.ts.map +1 -0
  29. package/dist/runtime/retry.js +181 -0
  30. package/dist/runtime/retry.js.map +1 -0
  31. package/dist/runtime/sequential.d.ts +1 -0
  32. package/dist/runtime/sequential.d.ts.map +1 -1
  33. package/dist/runtime/sequential.js +25 -16
  34. package/dist/runtime/sequential.js.map +1 -1
  35. package/dist/runtime/shared.d.ts +1 -0
  36. package/dist/runtime/shared.d.ts.map +1 -1
  37. package/dist/runtime/shared.js +25 -19
  38. package/dist/runtime/shared.js.map +1 -1
  39. package/dist/runtime/termination.d.ts +6 -1
  40. package/dist/runtime/termination.d.ts.map +1 -1
  41. package/dist/runtime/termination.js +75 -0
  42. package/dist/runtime/termination.js.map +1 -1
  43. package/dist/runtime/tools/built-in.d.ts +99 -0
  44. package/dist/runtime/tools/built-in.d.ts.map +1 -0
  45. package/dist/runtime/tools/built-in.js +577 -0
  46. package/dist/runtime/tools/built-in.js.map +1 -0
  47. package/dist/runtime/tools/vercel-ai.d.ts +67 -0
  48. package/dist/runtime/tools/vercel-ai.d.ts.map +1 -0
  49. package/dist/runtime/tools/vercel-ai.js +148 -0
  50. package/dist/runtime/tools/vercel-ai.js.map +1 -0
  51. package/dist/runtime/tools.d.ts +5 -268
  52. package/dist/runtime/tools.d.ts.map +1 -1
  53. package/dist/runtime/tools.js +7 -770
  54. package/dist/runtime/tools.js.map +1 -1
  55. package/dist/runtime/validation.d.ts.map +1 -1
  56. package/dist/runtime/validation.js +22 -0
  57. package/dist/runtime/validation.js.map +1 -1
  58. package/dist/runtime/wrap-up.d.ts +26 -0
  59. package/dist/runtime/wrap-up.d.ts.map +1 -0
  60. package/dist/runtime/wrap-up.js +178 -0
  61. package/dist/runtime/wrap-up.js.map +1 -0
  62. package/dist/types/benchmark.d.ts +276 -0
  63. package/dist/types/benchmark.d.ts.map +1 -0
  64. package/dist/types/benchmark.js +2 -0
  65. package/dist/types/benchmark.js.map +1 -0
  66. package/dist/types/events.d.ts +495 -0
  67. package/dist/types/events.d.ts.map +1 -0
  68. package/dist/types/events.js +2 -0
  69. package/dist/types/events.js.map +1 -0
  70. package/dist/types/replay.d.ts +169 -0
  71. package/dist/types/replay.d.ts.map +1 -0
  72. package/dist/types/replay.js +2 -0
  73. package/dist/types/replay.js.map +1 -0
  74. package/dist/types.d.ts +74 -935
  75. package/dist/types.d.ts.map +1 -1
  76. package/package.json +28 -1
  77. package/src/index.ts +7 -1
  78. package/src/runtime/broadcast.ts +50 -35
  79. package/src/runtime/coordinator.ts +84 -43
  80. package/src/runtime/engine.ts +6 -0
  81. package/src/runtime/ids.ts +41 -0
  82. package/src/runtime/logger.ts +152 -0
  83. package/src/runtime/retry.ts +270 -0
  84. package/src/runtime/sequential.ts +46 -31
  85. package/src/runtime/shared.ts +46 -35
  86. package/src/runtime/termination.ts +100 -0
  87. package/src/runtime/tools/built-in.ts +875 -0
  88. package/src/runtime/tools/vercel-ai.ts +269 -0
  89. package/src/runtime/tools.ts +60 -1255
  90. package/src/runtime/validation.ts +25 -0
  91. package/src/runtime/wrap-up.ts +257 -0
  92. package/src/types/benchmark.ts +300 -0
  93. package/src/types/events.ts +544 -0
  94. package/src/types/replay.ts +201 -0
  95. package/src/types.ts +174 -994
@@ -67,6 +67,39 @@ function isRecord$2(value) {
67
67
  return typeof value === "object" && value !== null && !Array.isArray(value);
68
68
  }
69
69
  //#endregion
70
+ //#region src/runtime/ids.ts
71
+ /**
72
+ * Repo-internal id and timing helpers used across all four protocols.
73
+ *
74
+ * Centralized here so a change to id format or fallback semantics happens in
75
+ * exactly one place — switching `protocol` must not change the run-id contract.
76
+ */
77
+ /**
78
+ * Generates a fresh run id using `globalThis.crypto.randomUUID`.
79
+ *
80
+ * Throws a `DogpileError` when no UUID source is available rather than falling
81
+ * back to a millisecond-based id (which collides under back-to-back runs in
82
+ * the same tick). Node 22+, Bun latest, and modern browsers all expose
83
+ * `crypto.randomUUID`; environments without it are unsupported by Dogpile.
84
+ */
85
+ function createRunId() {
86
+ const random = globalThis.crypto?.randomUUID?.();
87
+ if (typeof random === "string" && random.length > 0) return random;
88
+ throw new DogpileError({
89
+ code: "invalid-configuration",
90
+ message: "Dogpile requires globalThis.crypto.randomUUID to mint a run id. Run on Node 22+, Bun latest, or a modern browser ESM environment."
91
+ });
92
+ }
93
+ function nowMs() {
94
+ return globalThis.performance?.now() ?? Date.now();
95
+ }
96
+ function elapsedMs(startedAtMs) {
97
+ return Math.max(0, nowMs() - startedAtMs);
98
+ }
99
+ function providerCallIdFor(runId, oneBasedIndex) {
100
+ return `${runId}:provider-call:${oneBasedIndex}`;
101
+ }
102
+ //#endregion
70
103
  //#region src/runtime/defaults.ts
71
104
  function normalizeProtocol(protocol) {
72
105
  if (typeof protocol !== "string") return protocol;
@@ -638,6 +671,10 @@ function firstOf(...conditions) {
638
671
  * own normalized inputs so one stop class cannot accidentally satisfy another.
639
672
  */
640
673
  function evaluateTermination(condition, context) {
674
+ if (isTerminationFloorBlocked(condition, context)) return {
675
+ type: "continue",
676
+ condition
677
+ };
641
678
  switch (condition.kind) {
642
679
  case "budget": return evaluateBudget(condition, context);
643
680
  case "firstOf": return evaluateFirstOf(condition, context).decision;
@@ -695,6 +732,17 @@ function evaluateTerminationStop(condition, context) {
695
732
  return stopRecord(condition, decision);
696
733
  }
697
734
  /**
735
+ * Warn when a protocol-level termination floor cannot be satisfied because a
736
+ * lower iteration cap will stop the run first.
737
+ */
738
+ function warnOnProtocolTerminationMisconfiguration(protocol, terminate, warn = console.warn) {
739
+ const minTurns = protocolMinTurns(protocol);
740
+ if (minTurns === void 0 || !terminate) return;
741
+ const limitingIterationBudget = smallestIterationBudget(terminate);
742
+ if (limitingIterationBudget === void 0 || limitingIterationBudget >= minTurns) return;
743
+ warn(`[dogpile] protocol.minTurns (${minTurns}) exceeds terminate budget maxIterations (${limitingIterationBudget}); maxIterations will win.`);
744
+ }
745
+ /**
698
746
  * Combine independently evaluated termination decisions with SDK precedence.
699
747
  *
700
748
  * Budget caps win over judge decisions, and judge decisions win over
@@ -843,6 +891,48 @@ function stopPrecedence(reason) {
843
891
  if (reason.startsWith("judge:")) return 1;
844
892
  return 2;
845
893
  }
894
+ function isTerminationFloorBlocked(condition, context) {
895
+ if (condition.kind !== "convergence" && condition.kind !== "judge") return false;
896
+ const floor = protocolTerminationFloor(context.protocolConfig);
897
+ if (floor === void 0 || floor <= 0) return false;
898
+ return protocolProgress(context) < floor;
899
+ }
900
+ function protocolTerminationFloor(protocol) {
901
+ if (!protocol) return;
902
+ switch (protocol.kind) {
903
+ case "broadcast": return protocol.minRounds;
904
+ case "coordinator":
905
+ case "sequential":
906
+ case "shared": return protocol.minTurns;
907
+ }
908
+ }
909
+ function protocolProgress(context) {
910
+ return context.protocolIteration ?? context.iteration ?? context.transcript.length;
911
+ }
912
+ function protocolMinTurns(protocol) {
913
+ switch (protocol.kind) {
914
+ case "broadcast": return;
915
+ case "coordinator":
916
+ case "sequential":
917
+ case "shared": return protocol.minTurns;
918
+ }
919
+ }
920
+ function smallestIterationBudget(condition) {
921
+ switch (condition.kind) {
922
+ case "budget": return condition.maxIterations;
923
+ case "convergence":
924
+ case "judge": return;
925
+ case "firstOf": {
926
+ let smallest;
927
+ for (const child of condition.conditions) {
928
+ const budget = smallestIterationBudget(child);
929
+ if (budget === void 0) continue;
930
+ smallest = smallest === void 0 ? budget : Math.min(smallest, budget);
931
+ }
932
+ return smallest;
933
+ }
934
+ }
935
+ }
846
936
  function judgeStopDetail(decision, minScore) {
847
937
  return {
848
938
  decision: decision.type,
@@ -921,6 +1011,7 @@ function validateDogpileOptions(options) {
921
1011
  validateOptionalTemperature(options.temperature, "temperature");
922
1012
  validateOptionalBudgetCaps(options.budget, "budget");
923
1013
  validateOptionalTerminationCondition(options.terminate, "terminate");
1014
+ validateOptionalWrapUpHint(options.wrapUpHint, "wrapUpHint");
924
1015
  validateOptionalFunction(options.evaluate, "evaluate");
925
1016
  validateOptionalSeed(options.seed, "seed");
926
1017
  validateOptionalAbortSignal(options.signal, "signal");
@@ -941,6 +1032,7 @@ function validateEngineOptions(options) {
941
1032
  validateOptionalTemperature(options.temperature, "temperature");
942
1033
  validateOptionalBudgetCaps(options.budget, "budget");
943
1034
  validateOptionalTerminationCondition(options.terminate, "terminate");
1035
+ validateOptionalWrapUpHint(options.wrapUpHint, "wrapUpHint");
944
1036
  validateOptionalFunction(options.evaluate, "evaluate");
945
1037
  validateOptionalSeed(options.seed, "seed");
946
1038
  validateOptionalAbortSignal(options.signal, "signal");
@@ -973,10 +1065,12 @@ function validateProtocolConfig(value, path) {
973
1065
  case "sequential":
974
1066
  case "shared":
975
1067
  validateOptionalPositiveInteger(record.maxTurns, `${path}.maxTurns`);
1068
+ validateOptionalNonNegativeInteger(record.minTurns, `${path}.minTurns`);
976
1069
  if (kind === "shared") validateOptionalString(record.organizationalMemory, `${path}.organizationalMemory`);
977
1070
  return;
978
1071
  case "broadcast":
979
1072
  validateOptionalPositiveInteger(record.maxRounds, `${path}.maxRounds`);
1073
+ validateOptionalNonNegativeInteger(record.minRounds, `${path}.minRounds`);
980
1074
  return;
981
1075
  }
982
1076
  }
@@ -1144,6 +1238,20 @@ function validateJudgeRubric(value, path) {
1144
1238
  function validateOptionalTemperature(value, path) {
1145
1239
  validateOptionalNumberInRange(value, path, 0, 2);
1146
1240
  }
1241
+ function validateOptionalWrapUpHint(value, path) {
1242
+ if (value === void 0) return;
1243
+ const record = requireRecord(value, path);
1244
+ validateOptionalNonNegativeInteger(record.atIteration, `${path}.atIteration`);
1245
+ validateOptionalNumberInRange(record.atFraction, `${path}.atFraction`, 0, 1);
1246
+ validateOptionalFunction(record.inject, `${path}.inject`);
1247
+ if (record.atIteration === void 0 && record.atFraction === void 0) invalidConfiguration({
1248
+ path,
1249
+ rule: "object",
1250
+ message: "wrapUpHint must configure atIteration or atFraction.",
1251
+ expected: "WrapUpHintConfig with atIteration or atFraction",
1252
+ actual: value
1253
+ });
1254
+ }
1147
1255
  function validateOptionalSeed(value, path) {
1148
1256
  if (value === void 0) return;
1149
1257
  if (typeof value === "string") return;
@@ -1327,7 +1435,7 @@ function describeValue(value) {
1327
1435
  return typeof value;
1328
1436
  }
1329
1437
  //#endregion
1330
- //#region src/runtime/tools.ts
1438
+ //#region src/runtime/tools/built-in.ts
1331
1439
  var webSearchIdentity = {
1332
1440
  id: "dogpile.tools.webSearch",
1333
1441
  namespace: "dogpile",
@@ -1413,9 +1521,6 @@ function builtInDogpileToolIdentity(name) {
1413
1521
  function builtInDogpileToolInputSchema(name) {
1414
1522
  return name === "webSearch" ? webSearchInputSchema : codeExecInputSchema;
1415
1523
  }
1416
- /**
1417
- * Return the default permission declarations for one built-in tool name.
1418
- */
1419
1524
  function builtInDogpileToolPermissions(name) {
1420
1525
  return name === "webSearch" ? webSearchPermissions : codeExecPermissions;
1421
1526
  }
@@ -1426,348 +1531,91 @@ function validateBuiltInDogpileToolInput(name, input) {
1426
1531
  issues
1427
1532
  };
1428
1533
  }
1429
- /**
1430
- * Create the shared runtime tool executor used by every first-party protocol.
1431
- *
1432
- * @remarks
1433
- * The executor owns call id generation, read-only trace context construction,
1434
- * adapter validation, error normalization, and matched `tool-call` /
1435
- * `tool-result` events. Protocols only supply a normalized
1436
- * {@link RuntimeToolExecutionRequest}, which keeps tool execution independent
1437
- * of Coordinator, Sequential, Broadcast, or Shared control flow.
1438
- */
1439
- function createRuntimeToolExecutor(options) {
1440
- validateRuntimeToolRegistrations(options.tools);
1441
- const tools = Array.from(options.tools);
1442
- let callCount = 0;
1443
- return {
1444
- tools,
1445
- async execute(request) {
1446
- const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
1447
- const identity = tool?.identity ?? {
1448
- id: request.toolId,
1449
- name: request.toolId
1450
- };
1451
- const callIndex = callCount;
1452
- callCount += 1;
1453
- const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
1454
- const context = createExecutionContext(options, request, toolCallId);
1455
- options.emit?.({
1456
- type: "tool-call",
1457
- runId: options.runId,
1458
- at: (/* @__PURE__ */ new Date()).toISOString(),
1459
- toolCallId,
1534
+ function createWebSearchToolAdapter(options) {
1535
+ const identity = mergeIdentity(webSearchIdentity, options.identity);
1536
+ return normalizeBuiltInDogpileTool({
1537
+ name: "webSearch",
1538
+ ...options.identity ? { identity: options.identity } : {},
1539
+ ...options.permissions ? { permissions: options.permissions } : {},
1540
+ async execute(input, context) {
1541
+ const fetchImplementation = options.fetch ?? globalThis.fetch;
1542
+ if (!fetchImplementation) return {
1543
+ type: "error",
1544
+ toolCallId: context.toolCallId,
1460
1545
  tool: identity,
1461
- input: request.input,
1462
- ...request.agentId ? { agentId: request.agentId } : {},
1463
- ...request.role ? { role: request.role } : {}
1546
+ error: {
1547
+ code: "unavailable",
1548
+ message: "No fetch implementation is available for webSearch.",
1549
+ retryable: false
1550
+ }
1551
+ };
1552
+ const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
1553
+ const response = await fetchImplementation(request.url, {
1554
+ ...request.init,
1555
+ ...context.abortSignal ? { signal: context.abortSignal } : {}
1464
1556
  });
1465
- const result = await executeRuntimeTool(tool, identity, request.input, context);
1466
- options.emit?.({
1467
- type: "tool-result",
1468
- runId: options.runId,
1469
- at: (/* @__PURE__ */ new Date()).toISOString(),
1470
- toolCallId,
1557
+ if (!response.ok) throw {
1558
+ code: response.status >= 500 ? "unavailable" : "backend-error",
1559
+ message: `Web search backend returned HTTP ${response.status}.`,
1560
+ retryable: response.status === 408 || response.status === 429 || response.status >= 500,
1561
+ detail: {
1562
+ status: response.status,
1563
+ statusText: response.statusText
1564
+ }
1565
+ };
1566
+ const output = options.parseResponse ? await options.parseResponse(response, input, context) : await defaultWebSearchResponseParser(response);
1567
+ return {
1568
+ type: "success",
1569
+ toolCallId: context.toolCallId,
1471
1570
  tool: identity,
1472
- result,
1473
- ...request.agentId ? { agentId: request.agentId } : {},
1474
- ...request.role ? { role: request.role } : {}
1475
- });
1476
- return result;
1571
+ output
1572
+ };
1477
1573
  }
1478
- };
1479
- }
1480
- /**
1481
- * Return a JSON-serializable manifest for tools visible to a protocol run.
1482
- */
1483
- function runtimeToolManifest(tools) {
1484
- return tools.map((tool) => {
1485
- const inputSchema = {
1486
- kind: tool.inputSchema.kind,
1487
- schema: tool.inputSchema.schema,
1488
- ...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
1489
- };
1490
- return {
1491
- identity: runtimeToolIdentityManifest(tool.identity),
1492
- inputSchema,
1493
- permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
1494
- };
1495
1574
  });
1496
1575
  }
1497
- /**
1498
- * Return request metadata that makes runtime tools visible to provider
1499
- * adapters, or an empty object when no tools are available.
1500
- */
1501
- function runtimeToolAvailability(tools) {
1502
- const manifest = runtimeToolManifest(tools);
1503
- return manifest.length > 0 ? { tools: manifest } : {};
1504
- }
1505
- /**
1506
- * Execute normalized tool requests returned by a provider response.
1507
- */
1508
- async function executeModelResponseToolRequests(options) {
1509
- const toolCalls = [];
1510
- for (const request of options.response.toolRequests ?? []) {
1511
- const result = await options.executor.execute({
1512
- ...request,
1513
- agentId: request.agentId ?? options.agentId,
1514
- role: request.role ?? options.role,
1515
- turn: request.turn ?? options.turn,
1516
- metadata: mergeToolMetadata(options.metadata, request.metadata)
1517
- });
1518
- toolCalls.push({
1519
- toolCallId: result.toolCallId,
1520
- tool: result.tool,
1521
- input: request.input,
1522
- result
1523
- });
1524
- }
1525
- return toolCalls;
1526
- }
1527
- function runtimeToolIdentityManifest(identity) {
1528
- return {
1529
- id: identity.id,
1530
- name: identity.name,
1531
- ...identity.namespace ? { namespace: identity.namespace } : {},
1532
- ...identity.version ? { version: identity.version } : {},
1533
- ...identity.description ? { description: identity.description } : {}
1534
- };
1535
- }
1536
- function runtimeToolPermissionManifest(permission) {
1537
- if (permission.kind === "network") return {
1538
- kind: permission.kind,
1539
- ...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
1540
- ...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
1541
- };
1542
- if (permission.kind === "code-execution") return {
1543
- kind: permission.kind,
1544
- sandbox: permission.sandbox,
1545
- ...permission.languages ? { languages: Array.from(permission.languages) } : {},
1546
- ...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
1547
- };
1576
+ function createCodeExecToolAdapter(options) {
1577
+ const identity = mergeIdentity(codeExecIdentity, options.identity);
1578
+ const permissions = options.permissions ?? codeExecPermissionsFor(options.languages ?? codeExecLanguages, options.allowNetwork ?? false);
1548
1579
  return {
1549
- kind: permission.kind,
1550
- name: permission.name,
1551
- ...permission.description ? { description: permission.description } : {},
1552
- ...permission.metadata ? { metadata: permission.metadata } : {}
1553
- };
1554
- }
1555
- function validateCodeExecAdapterInput(input, options) {
1556
- const issues = [...validateCodeExecInput(input)];
1557
- const languages = options.languages ?? codeExecLanguages;
1558
- if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
1559
- code: "invalid-value",
1560
- path: "language",
1561
- message: "codeExec.language is not enabled for this adapter.",
1562
- detail: { allowed: Array.from(languages) }
1563
- });
1564
- const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
1565
- if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
1566
- code: "out-of-range",
1567
- path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
1568
- message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
1569
- detail: { maximum: options.maxTimeoutMs }
1570
- });
1571
- return issues.length === 0 ? { type: "valid" } : {
1572
- type: "invalid",
1573
- issues
1574
- };
1575
- }
1576
- function createExecutionContext(options, request, toolCallId) {
1577
- return {
1578
- runId: options.runId,
1579
- toolCallId,
1580
- protocol: options.protocol,
1581
- tier: options.tier,
1582
- ...request.agentId ? { agentId: request.agentId } : {},
1583
- ...request.role ? { role: request.role } : {},
1584
- ...request.turn !== void 0 ? { turn: request.turn } : {},
1585
- ...options.getTrace ? { trace: options.getTrace() } : {},
1586
- ...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
1587
- ...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
1588
- };
1589
- }
1590
- async function executeRuntimeTool(tool, identity, input, context) {
1591
- if (!tool) return {
1592
- type: "error",
1593
- toolCallId: context.toolCallId,
1594
- tool: identity,
1595
- error: {
1596
- code: "unavailable",
1597
- message: `Runtime tool "${identity.id}" is not registered.`,
1598
- retryable: false
1599
- }
1600
- };
1601
- const validation = validateRuntimeToolInput(tool, input);
1602
- if (validation.type === "invalid") return {
1603
- type: "error",
1604
- toolCallId: context.toolCallId,
1605
- tool: identity,
1606
- error: {
1607
- code: "invalid-input",
1608
- message: "Runtime tool input failed validation.",
1609
- retryable: false,
1610
- detail: { issues: validation.issues.map((issue) => ({
1611
- code: issue.code,
1612
- path: issue.path,
1613
- message: issue.message,
1614
- ...issue.detail ? { detail: issue.detail } : {}
1615
- })) }
1616
- }
1617
- };
1618
- try {
1619
- return await tool.execute(input, context);
1620
- } catch (error) {
1621
- return {
1622
- type: "error",
1623
- toolCallId: context.toolCallId,
1624
- tool: identity,
1625
- error: normalizeRuntimeToolAdapterError(error)
1626
- };
1627
- }
1628
- }
1629
- function validateRuntimeToolInput(tool, input) {
1630
- if (typeof tool.validateInput !== "function") return { type: "valid" };
1631
- return tool.validateInput(input);
1632
- }
1633
- function mergeToolMetadata(base, request) {
1634
- return {
1635
- ...base ?? {},
1636
- ...request ?? {}
1637
- };
1638
- }
1639
- function defaultToolCallId(runId, callIndex) {
1640
- return `${runId}:tool-${callIndex + 1}`;
1641
- }
1642
- /**
1643
- * Convert an unknown adapter failure into Dogpile's serializable error data.
1644
- */
1645
- function normalizeRuntimeToolAdapterError(error) {
1646
- if (isRuntimeToolAdapterError(error)) return error;
1647
- if (error instanceof DOMException && error.name === "AbortError") return {
1648
- code: "aborted",
1649
- message: error.message || "Tool execution was aborted.",
1650
- retryable: true,
1651
- detail: { name: error.name }
1652
- };
1653
- if (error instanceof Error) return {
1654
- code: "backend-error",
1655
- message: error.message,
1656
- retryable: false,
1657
- detail: { name: error.name }
1658
- };
1659
- return {
1660
- code: "unknown",
1661
- message: "Tool execution failed with a non-Error value.",
1662
- retryable: false,
1663
- detail: { valueType: typeof error }
1664
- };
1665
- }
1666
- /**
1667
- * Create Dogpile's built-in fetch-based web search adapter.
1668
- *
1669
- * @remarks
1670
- * The adapter is backend-neutral: by default it sends a GET request with
1671
- * `q` and `limit` query parameters, then accepts either `{ results: [...] }`
1672
- * or a bare array of result objects from the response JSON. Callers can replace
1673
- * request construction or response parsing for a specific search API while
1674
- * keeping Dogpile's shared runtime tool contract, identity, permissions, input
1675
- * validation, and serializable errors.
1676
- */
1677
- function createWebSearchToolAdapter(options) {
1678
- const identity = mergeIdentity(webSearchIdentity, options.identity);
1679
- return normalizeBuiltInDogpileTool({
1680
- name: "webSearch",
1681
- ...options.identity ? { identity: options.identity } : {},
1682
- ...options.permissions ? { permissions: options.permissions } : {},
1683
- async execute(input, context) {
1684
- const fetchImplementation = options.fetch ?? globalThis.fetch;
1685
- if (!fetchImplementation) return {
1686
- type: "error",
1687
- toolCallId: context.toolCallId,
1688
- tool: identity,
1689
- error: {
1690
- code: "unavailable",
1691
- message: "No fetch implementation is available for webSearch.",
1692
- retryable: false
1693
- }
1694
- };
1695
- const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
1696
- const response = await fetchImplementation(request.url, {
1697
- ...request.init,
1698
- ...context.abortSignal ? { signal: context.abortSignal } : {}
1699
- });
1700
- if (!response.ok) throw {
1701
- code: response.status >= 500 ? "unavailable" : "backend-error",
1702
- message: `Web search backend returned HTTP ${response.status}.`,
1703
- retryable: response.status === 408 || response.status === 429 || response.status >= 500,
1704
- detail: {
1705
- status: response.status,
1706
- statusText: response.statusText
1707
- }
1708
- };
1709
- const output = options.parseResponse ? await options.parseResponse(response, input, context) : await defaultWebSearchResponseParser(response);
1710
- return {
1711
- type: "success",
1712
- toolCallId: context.toolCallId,
1713
- tool: identity,
1714
- output
1715
- };
1716
- }
1717
- });
1718
- }
1719
- /**
1720
- * Create Dogpile's built-in code execution adapter around a caller-owned sandbox.
1721
- *
1722
- * @remarks
1723
- * Dogpile core stays runtime-portable and never evaluates code itself. This
1724
- * adapter supplies the stable `codeExec` identity, schema, permissions,
1725
- * validation, timeout defaults, abort handling, and serializable errors while
1726
- * the host application owns the sandbox boundary.
1727
- */
1728
- function createCodeExecToolAdapter(options) {
1729
- const identity = mergeIdentity(codeExecIdentity, options.identity);
1730
- const permissions = options.permissions ?? codeExecPermissionsFor(options.languages ?? codeExecLanguages, options.allowNetwork ?? false);
1731
- return {
1732
- identity,
1733
- inputSchema: codeExecInputSchemaFor(options.languages ?? codeExecLanguages),
1734
- permissions,
1735
- validateInput: (input) => validateCodeExecAdapterInput(input, options),
1736
- async execute(input, context) {
1737
- const validation = validateCodeExecAdapterInput(input, options);
1738
- if (validation.type === "invalid") return {
1739
- type: "error",
1740
- toolCallId: context.toolCallId,
1741
- tool: identity,
1742
- error: {
1743
- code: "invalid-input",
1744
- message: "Invalid codeExec tool input.",
1745
- retryable: false,
1746
- detail: { issues: validation.issues }
1747
- }
1748
- };
1749
- const timeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
1750
- const executionInput = timeoutMs === void 0 ? input : {
1751
- ...input,
1752
- timeoutMs
1753
- };
1754
- try {
1755
- const output = await executeSandboxWithPolicy(options.execute, executionInput, context, timeoutMs);
1756
- return {
1757
- type: "success",
1758
- toolCallId: context.toolCallId,
1759
- tool: identity,
1760
- output
1761
- };
1762
- } catch (error) {
1763
- return {
1764
- type: "error",
1765
- toolCallId: context.toolCallId,
1766
- tool: identity,
1767
- error: normalizeRuntimeToolAdapterError(error)
1768
- };
1769
- }
1770
- }
1580
+ identity,
1581
+ inputSchema: codeExecInputSchemaFor(options.languages ?? codeExecLanguages),
1582
+ permissions,
1583
+ validateInput: (input) => validateCodeExecAdapterInput(input, options),
1584
+ async execute(input, context) {
1585
+ const validation = validateCodeExecAdapterInput(input, options);
1586
+ if (validation.type === "invalid") return {
1587
+ type: "error",
1588
+ toolCallId: context.toolCallId,
1589
+ tool: identity,
1590
+ error: {
1591
+ code: "invalid-input",
1592
+ message: "Invalid codeExec tool input.",
1593
+ retryable: false,
1594
+ detail: { issues: serializeValidationIssues(validation.issues) }
1595
+ }
1596
+ };
1597
+ const timeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
1598
+ const executionInput = timeoutMs === void 0 ? input : {
1599
+ ...input,
1600
+ timeoutMs
1601
+ };
1602
+ try {
1603
+ const output = await executeSandboxWithPolicy(options.execute, executionInput, context, timeoutMs);
1604
+ return {
1605
+ type: "success",
1606
+ toolCallId: context.toolCallId,
1607
+ tool: identity,
1608
+ output
1609
+ };
1610
+ } catch (error) {
1611
+ return {
1612
+ type: "error",
1613
+ toolCallId: context.toolCallId,
1614
+ tool: identity,
1615
+ error: normalizeRuntimeToolAdapterError(error)
1616
+ };
1617
+ }
1618
+ }
1771
1619
  };
1772
1620
  }
1773
1621
  function normalizeBuiltInDogpileTool(definition) {
@@ -1796,9 +1644,6 @@ function normalizeBuiltInDogpileTool(definition) {
1796
1644
  }
1797
1645
  }
1798
1646
  }
1799
- /**
1800
- * Normalize configured built-in Dogpile tool executors into runtime tools.
1801
- */
1802
1647
  function normalizeBuiltInDogpileTools(tools) {
1803
1648
  const normalized = [];
1804
1649
  if (tools.webSearch) normalized.push(normalizeBuiltInDogpileTool(asWebSearchDefinition(tools.webSearch)));
@@ -1815,7 +1660,7 @@ async function executeBuiltInTool(identity, execute, input, context, name) {
1815
1660
  code: "invalid-input",
1816
1661
  message: `Invalid ${name} tool input.`,
1817
1662
  retryable: false,
1818
- detail: { issues: validation.issues }
1663
+ detail: { issues: serializeValidationIssues(validation.issues) }
1819
1664
  }
1820
1665
  };
1821
1666
  try {
@@ -1850,6 +1695,27 @@ function mergeIdentity(defaultIdentity, options) {
1850
1695
  ...options.description !== void 0 ? { description: options.description } : {}
1851
1696
  };
1852
1697
  }
1698
+ function validateCodeExecAdapterInput(input, options) {
1699
+ const issues = [...validateCodeExecInput(input)];
1700
+ const languages = options.languages ?? codeExecLanguages;
1701
+ if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
1702
+ code: "invalid-value",
1703
+ path: "language",
1704
+ message: "codeExec.language is not enabled for this adapter.",
1705
+ detail: { allowed: Array.from(languages) }
1706
+ });
1707
+ const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
1708
+ if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
1709
+ code: "out-of-range",
1710
+ path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
1711
+ message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
1712
+ detail: { maximum: options.maxTimeoutMs }
1713
+ });
1714
+ return issues.length === 0 ? { type: "valid" } : {
1715
+ type: "invalid",
1716
+ issues
1717
+ };
1718
+ }
1853
1719
  function defaultWebSearchRequest(options, input, _context) {
1854
1720
  const url = new URL(String(options.endpoint));
1855
1721
  url.searchParams.set("q", input.query);
@@ -1969,107 +1835,471 @@ function jsonString(value, fieldName) {
1969
1835
  return value;
1970
1836
  }
1971
1837
  function optionalJsonString(value, fieldName) {
1972
- if (value === void 0) return;
1838
+ if (value === void 0) return void 0;
1973
1839
  if (typeof value !== "string") throw {
1974
1840
  code: "backend-error",
1975
1841
  message: `Web search result ${fieldName} must be a string when present.`,
1976
1842
  retryable: false
1977
1843
  };
1978
- return value;
1844
+ return value;
1845
+ }
1846
+ function optionalJsonObject(value, fieldName) {
1847
+ if (value === void 0) return void 0;
1848
+ if (!isJsonObject(value)) throw {
1849
+ code: "backend-error",
1850
+ message: `Web search result ${fieldName} must be a JSON object when present.`,
1851
+ retryable: false
1852
+ };
1853
+ return value;
1854
+ }
1855
+ function isJsonObject(value) {
1856
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1857
+ }
1858
+ function validateWebSearchInput(input) {
1859
+ const issues = [];
1860
+ if (typeof input.query !== "string") issues.push({
1861
+ code: input.query === void 0 ? "missing-field" : "invalid-type",
1862
+ path: "query",
1863
+ message: "webSearch.query must be a string."
1864
+ });
1865
+ else if (input.query.trim().length === 0) issues.push({
1866
+ code: "invalid-value",
1867
+ path: "query",
1868
+ message: "webSearch.query must not be empty."
1869
+ });
1870
+ if (input.maxResults !== void 0) {
1871
+ if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
1872
+ code: "invalid-type",
1873
+ path: "maxResults",
1874
+ message: "webSearch.maxResults must be a finite number."
1875
+ });
1876
+ else if (input.maxResults < 1) issues.push({
1877
+ code: "out-of-range",
1878
+ path: "maxResults",
1879
+ message: "webSearch.maxResults must be greater than or equal to 1.",
1880
+ detail: { minimum: 1 }
1881
+ });
1882
+ }
1883
+ return issues;
1884
+ }
1885
+ function validateCodeExecInput(input) {
1886
+ const issues = [];
1887
+ if (typeof input.language !== "string") issues.push({
1888
+ code: input.language === void 0 ? "missing-field" : "invalid-type",
1889
+ path: "language",
1890
+ message: "codeExec.language must be a string."
1891
+ });
1892
+ else if (!isCodeExecLanguage(input.language)) issues.push({
1893
+ code: "invalid-value",
1894
+ path: "language",
1895
+ message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
1896
+ detail: { allowed: [
1897
+ "javascript",
1898
+ "typescript",
1899
+ "python",
1900
+ "bash",
1901
+ "shell"
1902
+ ] }
1903
+ });
1904
+ if (typeof input.code !== "string") issues.push({
1905
+ code: input.code === void 0 ? "missing-field" : "invalid-type",
1906
+ path: "code",
1907
+ message: "codeExec.code must be a string."
1908
+ });
1909
+ if (input.timeoutMs !== void 0) {
1910
+ if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
1911
+ code: "invalid-type",
1912
+ path: "timeoutMs",
1913
+ message: "codeExec.timeoutMs must be a finite number."
1914
+ });
1915
+ else if (input.timeoutMs < 1) issues.push({
1916
+ code: "out-of-range",
1917
+ path: "timeoutMs",
1918
+ message: "codeExec.timeoutMs must be greater than or equal to 1.",
1919
+ detail: { minimum: 1 }
1920
+ });
1921
+ }
1922
+ return issues;
1923
+ }
1924
+ function isCodeExecLanguage(value) {
1925
+ return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
1926
+ }
1927
+ function normalizeRuntimeToolAdapterError(error) {
1928
+ if (isRuntimeToolAdapterError(error)) return error;
1929
+ if (error instanceof DOMException && error.name === "AbortError") return {
1930
+ code: "aborted",
1931
+ message: error.message || "Tool execution was aborted.",
1932
+ retryable: true,
1933
+ detail: { name: error.name }
1934
+ };
1935
+ if (error instanceof Error) return {
1936
+ code: "backend-error",
1937
+ message: error.message,
1938
+ retryable: false,
1939
+ detail: { name: error.name }
1940
+ };
1941
+ return {
1942
+ code: "unknown",
1943
+ message: "Tool execution failed with a non-Error value.",
1944
+ retryable: false,
1945
+ detail: { valueType: typeof error }
1946
+ };
1947
+ }
1948
+ function isRuntimeToolAdapterError(error) {
1949
+ if (typeof error !== "object" || error === null || !("code" in error) || !("message" in error)) return false;
1950
+ const candidate = error;
1951
+ return isRuntimeToolAdapterErrorCode(candidate.code) && typeof candidate.message === "string";
1952
+ }
1953
+ function isRuntimeToolAdapterErrorCode(value) {
1954
+ return value === "invalid-input" || value === "permission-denied" || value === "timeout" || value === "aborted" || value === "unavailable" || value === "backend-error" || value === "unknown";
1955
+ }
1956
+ function serializeValidationIssues(issues) {
1957
+ return issues.map((issue) => ({
1958
+ code: issue.code,
1959
+ path: issue.path,
1960
+ message: issue.message,
1961
+ ...issue.detail !== void 0 ? { detail: issue.detail } : {}
1962
+ }));
1963
+ }
1964
+ //#endregion
1965
+ //#region src/runtime/tools.ts
1966
+ /**
1967
+ * Create the shared runtime tool executor used by every first-party protocol.
1968
+ *
1969
+ * @remarks
1970
+ * The executor owns call id generation, read-only trace context construction,
1971
+ * adapter validation, error normalization, and matched `tool-call` /
1972
+ * `tool-result` events. Protocols only supply a normalized
1973
+ * {@link RuntimeToolExecutionRequest}, which keeps tool execution independent
1974
+ * of Coordinator, Sequential, Broadcast, or Shared control flow.
1975
+ */
1976
+ function createRuntimeToolExecutor(options) {
1977
+ validateRuntimeToolRegistrations(options.tools);
1978
+ const tools = Array.from(options.tools);
1979
+ let callCount = 0;
1980
+ return {
1981
+ tools,
1982
+ async execute(request) {
1983
+ const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
1984
+ const identity = tool?.identity ?? {
1985
+ id: request.toolId,
1986
+ name: request.toolId
1987
+ };
1988
+ const callIndex = callCount;
1989
+ callCount += 1;
1990
+ const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
1991
+ const context = createExecutionContext(options, request, toolCallId);
1992
+ options.emit?.({
1993
+ type: "tool-call",
1994
+ runId: options.runId,
1995
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1996
+ toolCallId,
1997
+ tool: identity,
1998
+ input: request.input,
1999
+ ...request.agentId ? { agentId: request.agentId } : {},
2000
+ ...request.role ? { role: request.role } : {}
2001
+ });
2002
+ const result = await executeRuntimeTool(tool, identity, request.input, context);
2003
+ options.emit?.({
2004
+ type: "tool-result",
2005
+ runId: options.runId,
2006
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2007
+ toolCallId,
2008
+ tool: identity,
2009
+ result,
2010
+ ...request.agentId ? { agentId: request.agentId } : {},
2011
+ ...request.role ? { role: request.role } : {}
2012
+ });
2013
+ return result;
2014
+ }
2015
+ };
2016
+ }
2017
+ /**
2018
+ * Return a JSON-serializable manifest for tools visible to a protocol run.
2019
+ */
2020
+ function runtimeToolManifest(tools) {
2021
+ return tools.map((tool) => {
2022
+ const inputSchema = {
2023
+ kind: tool.inputSchema.kind,
2024
+ schema: tool.inputSchema.schema,
2025
+ ...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
2026
+ };
2027
+ return {
2028
+ identity: runtimeToolIdentityManifest(tool.identity),
2029
+ inputSchema,
2030
+ permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
2031
+ };
2032
+ });
2033
+ }
2034
+ /**
2035
+ * Return request metadata that makes runtime tools visible to provider
2036
+ * adapters, or an empty object when no tools are available.
2037
+ */
2038
+ function runtimeToolAvailability(tools) {
2039
+ const manifest = runtimeToolManifest(tools);
2040
+ return manifest.length > 0 ? { tools: manifest } : {};
2041
+ }
2042
+ /**
2043
+ * Execute normalized tool requests returned by a provider response.
2044
+ */
2045
+ async function executeModelResponseToolRequests(options) {
2046
+ const toolCalls = [];
2047
+ for (const request of options.response.toolRequests ?? []) {
2048
+ const result = await options.executor.execute({
2049
+ ...request,
2050
+ agentId: request.agentId ?? options.agentId,
2051
+ role: request.role ?? options.role,
2052
+ turn: request.turn ?? options.turn,
2053
+ metadata: mergeToolMetadata(options.metadata, request.metadata)
2054
+ });
2055
+ toolCalls.push({
2056
+ toolCallId: result.toolCallId,
2057
+ tool: result.tool,
2058
+ input: request.input,
2059
+ result
2060
+ });
2061
+ }
2062
+ return toolCalls;
2063
+ }
2064
+ function runtimeToolIdentityManifest(identity) {
2065
+ return {
2066
+ id: identity.id,
2067
+ name: identity.name,
2068
+ ...identity.namespace ? { namespace: identity.namespace } : {},
2069
+ ...identity.version ? { version: identity.version } : {},
2070
+ ...identity.description ? { description: identity.description } : {}
2071
+ };
2072
+ }
2073
+ function runtimeToolPermissionManifest(permission) {
2074
+ if (permission.kind === "network") return {
2075
+ kind: permission.kind,
2076
+ ...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
2077
+ ...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
2078
+ };
2079
+ if (permission.kind === "code-execution") return {
2080
+ kind: permission.kind,
2081
+ sandbox: permission.sandbox,
2082
+ ...permission.languages ? { languages: Array.from(permission.languages) } : {},
2083
+ ...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
2084
+ };
2085
+ return {
2086
+ kind: permission.kind,
2087
+ name: permission.name,
2088
+ ...permission.description ? { description: permission.description } : {},
2089
+ ...permission.metadata ? { metadata: permission.metadata } : {}
2090
+ };
2091
+ }
2092
+ function createExecutionContext(options, request, toolCallId) {
2093
+ return {
2094
+ runId: options.runId,
2095
+ toolCallId,
2096
+ protocol: options.protocol,
2097
+ tier: options.tier,
2098
+ ...request.agentId ? { agentId: request.agentId } : {},
2099
+ ...request.role ? { role: request.role } : {},
2100
+ ...request.turn !== void 0 ? { turn: request.turn } : {},
2101
+ ...options.getTrace ? { trace: options.getTrace() } : {},
2102
+ ...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
2103
+ ...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
2104
+ };
2105
+ }
2106
+ async function executeRuntimeTool(tool, identity, input, context) {
2107
+ if (!tool) return {
2108
+ type: "error",
2109
+ toolCallId: context.toolCallId,
2110
+ tool: identity,
2111
+ error: {
2112
+ code: "unavailable",
2113
+ message: `Runtime tool "${identity.id}" is not registered.`,
2114
+ retryable: false
2115
+ }
2116
+ };
2117
+ const validation = tool.validateInput?.(input);
2118
+ if (validation?.type === "invalid") return {
2119
+ type: "error",
2120
+ toolCallId: context.toolCallId,
2121
+ tool: identity,
2122
+ error: {
2123
+ code: "invalid-input",
2124
+ message: "Runtime tool input failed validation.",
2125
+ retryable: false,
2126
+ detail: { issues: validation.issues.map((issue) => ({
2127
+ code: issue.code,
2128
+ path: issue.path,
2129
+ message: issue.message,
2130
+ ...issue.detail ? { detail: issue.detail } : {}
2131
+ })) }
2132
+ }
2133
+ };
2134
+ try {
2135
+ return await tool.execute(input, context);
2136
+ } catch (error) {
2137
+ return {
2138
+ type: "error",
2139
+ toolCallId: context.toolCallId,
2140
+ tool: identity,
2141
+ error: normalizeRuntimeToolAdapterError(error)
2142
+ };
2143
+ }
2144
+ }
2145
+ function mergeToolMetadata(base, request) {
2146
+ return {
2147
+ ...base ?? {},
2148
+ ...request ?? {}
2149
+ };
2150
+ }
2151
+ function defaultToolCallId(runId, callIndex) {
2152
+ return `${runId}:tool-${callIndex + 1}`;
2153
+ }
2154
+ //#endregion
2155
+ //#region src/runtime/wrap-up.ts
2156
+ function createWrapUpHintController(options) {
2157
+ const hint = options.wrapUpHint;
2158
+ const effectiveBudget = effectiveWrapUpBudget(options.budget, options.terminate);
2159
+ let emitted = false;
2160
+ return {
2161
+ context(context) {
2162
+ return wrapUpEvaluationContext({
2163
+ ...context,
2164
+ tier: options.tier,
2165
+ ...effectiveBudget !== void 0 ? { budget: effectiveBudget } : {}
2166
+ });
2167
+ },
2168
+ inject(messages, context) {
2169
+ if (!hint || emitted) return messages;
2170
+ const evaluationContext = wrapUpEvaluationContext({
2171
+ ...context,
2172
+ tier: options.tier,
2173
+ ...effectiveBudget !== void 0 ? { budget: effectiveBudget } : {}
2174
+ });
2175
+ if (!shouldInjectWrapUpHint(hint, evaluationContext)) return messages;
2176
+ const content = (hint.inject ?? defaultWrapUpHint)(evaluationContext);
2177
+ emitted = true;
2178
+ return [
2179
+ messages[0] ?? {
2180
+ role: "system",
2181
+ content: ""
2182
+ },
2183
+ {
2184
+ role: "system",
2185
+ content
2186
+ },
2187
+ ...messages.slice(1)
2188
+ ];
2189
+ }
2190
+ };
1979
2191
  }
1980
- function optionalJsonObject(value, fieldName) {
1981
- if (value === void 0) return;
1982
- if (!isJsonObject(value)) throw {
1983
- code: "backend-error",
1984
- message: `Web search result ${fieldName} must be a JSON object when present.`,
1985
- retryable: false
2192
+ function wrapUpEvaluationContext(options) {
2193
+ const iteration = options.iteration ?? options.transcript.length;
2194
+ const elapsedMs = options.elapsedMs;
2195
+ return {
2196
+ runId: options.runId,
2197
+ protocol: options.protocol,
2198
+ tier: options.tier,
2199
+ cost: options.cost,
2200
+ events: options.events,
2201
+ transcript: options.transcript,
2202
+ ...options.protocolConfig !== void 0 ? { protocolConfig: options.protocolConfig } : {},
2203
+ ...iteration !== void 0 ? { iteration } : {},
2204
+ ...options.protocolIteration !== void 0 ? { protocolIteration: options.protocolIteration } : {},
2205
+ ...elapsedMs !== void 0 ? { elapsedMs } : {},
2206
+ ...options.budget !== void 0 ? { budget: options.budget } : {},
2207
+ ...options.budget !== void 0 ? { remainingBudget: remainingBudget(options.budget, {
2208
+ cost: options.cost,
2209
+ iteration,
2210
+ elapsedMs
2211
+ }) } : {},
2212
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
1986
2213
  };
1987
- return value;
1988
2214
  }
1989
- function isJsonObject(value) {
1990
- return typeof value === "object" && value !== null && !Array.isArray(value);
2215
+ function shouldInjectWrapUpHint(hint, context) {
2216
+ const iteration = context.iteration ?? context.transcript.length;
2217
+ if (hint.atIteration !== void 0 && iteration >= hint.atIteration) return true;
2218
+ if (hint.atFraction === void 0 || context.budget === void 0) return false;
2219
+ const fraction = hint.atFraction;
2220
+ return capFractionReached(iteration, context.budget.maxIterations, fraction) || capFractionReached(context.elapsedMs, context.budget.timeoutMs, fraction);
1991
2221
  }
1992
- function validateWebSearchInput(input) {
1993
- const issues = [];
1994
- if (typeof input.query !== "string") issues.push({
1995
- code: input.query === void 0 ? "missing-field" : "invalid-type",
1996
- path: "query",
1997
- message: "webSearch.query must be a string."
1998
- });
1999
- else if (input.query.trim().length === 0) issues.push({
2000
- code: "invalid-value",
2001
- path: "query",
2002
- message: "webSearch.query must not be empty."
2003
- });
2004
- if (input.maxResults !== void 0) {
2005
- if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
2006
- code: "invalid-type",
2007
- path: "maxResults",
2008
- message: "webSearch.maxResults must be a finite number."
2009
- });
2010
- else if (input.maxResults < 1) issues.push({
2011
- code: "out-of-range",
2012
- path: "maxResults",
2013
- message: "webSearch.maxResults must be greater than or equal to 1.",
2014
- detail: { minimum: 1 }
2015
- });
2016
- }
2017
- return issues;
2222
+ function capFractionReached(current, limit, fraction) {
2223
+ if (current === void 0 || limit === void 0 || limit <= 0) return false;
2224
+ return current / limit >= fraction;
2018
2225
  }
2019
- function validateCodeExecInput(input) {
2020
- const issues = [];
2021
- if (typeof input.language !== "string") issues.push({
2022
- code: input.language === void 0 ? "missing-field" : "invalid-type",
2023
- path: "language",
2024
- message: "codeExec.language must be a string."
2025
- });
2026
- else if (!isCodeExecLanguage(input.language)) issues.push({
2027
- code: "invalid-value",
2028
- path: "language",
2029
- message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
2030
- detail: { allowed: [
2031
- "javascript",
2032
- "typescript",
2033
- "python",
2034
- "bash",
2035
- "shell"
2036
- ] }
2037
- });
2038
- if (typeof input.code !== "string") issues.push({
2039
- code: input.code === void 0 ? "missing-field" : "invalid-type",
2040
- path: "code",
2041
- message: "codeExec.code must be a string."
2042
- });
2043
- if (input.timeoutMs !== void 0) {
2044
- if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
2045
- code: "invalid-type",
2046
- path: "timeoutMs",
2047
- message: "codeExec.timeoutMs must be a finite number."
2048
- });
2049
- else if (input.timeoutMs < 1) issues.push({
2050
- code: "out-of-range",
2051
- path: "timeoutMs",
2052
- message: "codeExec.timeoutMs must be greater than or equal to 1.",
2053
- detail: { minimum: 1 }
2054
- });
2226
+ function remainingBudget(budget, current) {
2227
+ return {
2228
+ ...budget.maxIterations !== void 0 && current.iteration !== void 0 ? { iterations: Math.max(0, budget.maxIterations - current.iteration) } : {},
2229
+ ...budget.timeoutMs !== void 0 && current.elapsedMs !== void 0 ? { timeoutMs: Math.max(0, budget.timeoutMs - current.elapsedMs) } : {},
2230
+ ...budget.maxUsd !== void 0 ? { usd: Math.max(0, budget.maxUsd - current.cost.usd) } : {},
2231
+ ...budget.maxTokens !== void 0 ? { tokens: Math.max(0, budget.maxTokens - current.cost.totalTokens) } : {}
2232
+ };
2233
+ }
2234
+ function defaultWrapUpHint(context) {
2235
+ const parts = [];
2236
+ const remaining = context.remainingBudget;
2237
+ if (remaining?.iterations !== void 0) {
2238
+ const label = remaining.iterations === 1 ? "turn" : "turns";
2239
+ parts.push(`${remaining.iterations} ${label} remaining`);
2055
2240
  }
2056
- return issues;
2241
+ if (remaining?.timeoutMs !== void 0) parts.push(`${formatRemainingTime(remaining.timeoutMs)} remaining`);
2242
+ return `[wrap-up] ${parts.length === 0 ? "You are approaching a configured hard limit." : `You are approaching a hard limit with ${parts.join(" and ")}.`} If you have enough context, package your work now and return a final-ready answer.`;
2243
+ }
2244
+ function formatRemainingTime(timeoutMs) {
2245
+ if (timeoutMs >= 1e3) return `${(timeoutMs / 1e3).toFixed(1)}s`;
2246
+ return `${timeoutMs}ms`;
2247
+ }
2248
+ function effectiveWrapUpBudget(budget, terminate) {
2249
+ const terminationBudget = budgetCapsFromTermination(terminate);
2250
+ if (!budget && !terminationBudget) return;
2251
+ const maxUsd = minCap(budget?.maxUsd, terminationBudget?.maxUsd);
2252
+ const maxTokens = minCap(budget?.maxTokens, terminationBudget?.maxTokens);
2253
+ const maxIterations = minCap(budget?.maxIterations, terminationBudget?.maxIterations);
2254
+ const timeoutMs = minCap(budget?.timeoutMs, terminationBudget?.timeoutMs);
2255
+ return {
2256
+ ...maxUsd !== void 0 ? { maxUsd } : {},
2257
+ ...maxTokens !== void 0 ? { maxTokens } : {},
2258
+ ...maxIterations !== void 0 ? { maxIterations } : {},
2259
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
2260
+ };
2057
2261
  }
2058
- function isCodeExecLanguage(value) {
2059
- return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
2262
+ function budgetCapsFromTermination(condition) {
2263
+ if (!condition) return;
2264
+ switch (condition.kind) {
2265
+ case "budget": return {
2266
+ ...condition.maxUsd !== void 0 ? { maxUsd: condition.maxUsd } : {},
2267
+ ...condition.maxTokens !== void 0 ? { maxTokens: condition.maxTokens } : {},
2268
+ ...condition.maxIterations !== void 0 ? { maxIterations: condition.maxIterations } : {},
2269
+ ...condition.timeoutMs !== void 0 ? { timeoutMs: condition.timeoutMs } : {}
2270
+ };
2271
+ case "firstOf": {
2272
+ let merged;
2273
+ for (const child of condition.conditions) merged = mergeBudgetCaps(merged, budgetCapsFromTermination(child));
2274
+ return merged;
2275
+ }
2276
+ case "convergence":
2277
+ case "judge": return;
2278
+ }
2060
2279
  }
2061
- function isRuntimeToolAdapterError(error) {
2062
- if (typeof error !== "object" || error === null || !("code" in error) || !("message" in error)) return false;
2063
- const candidate = error;
2064
- return isRuntimeToolAdapterErrorCode(candidate.code) && typeof candidate.message === "string";
2280
+ function mergeBudgetCaps(left, right) {
2281
+ if (!left) return right;
2282
+ if (!right) return left;
2283
+ const maxUsd = minCap(left.maxUsd, right.maxUsd);
2284
+ const maxTokens = minCap(left.maxTokens, right.maxTokens);
2285
+ const maxIterations = minCap(left.maxIterations, right.maxIterations);
2286
+ const timeoutMs = minCap(left.timeoutMs, right.timeoutMs);
2287
+ return {
2288
+ ...maxUsd !== void 0 ? { maxUsd } : {},
2289
+ ...maxTokens !== void 0 ? { maxTokens } : {},
2290
+ ...maxIterations !== void 0 ? { maxIterations } : {},
2291
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
2292
+ };
2065
2293
  }
2066
- function isRuntimeToolAdapterErrorCode(value) {
2067
- return value === "invalid-input" || value === "permission-denied" || value === "timeout" || value === "aborted" || value === "unavailable" || value === "backend-error" || value === "unknown";
2294
+ function minCap(left, right) {
2295
+ if (left === void 0) return right;
2296
+ if (right === void 0) return left;
2297
+ return Math.min(left, right);
2068
2298
  }
2069
2299
  //#endregion
2070
2300
  //#region src/runtime/broadcast.ts
2071
2301
  async function runBroadcast(options) {
2072
- const runId = createRunId$3();
2302
+ const runId = createRunId();
2073
2303
  const events = [];
2074
2304
  const transcript = [];
2075
2305
  const protocolDecisions = [];
@@ -2078,9 +2308,17 @@ async function runBroadcast(options) {
2078
2308
  const maxRounds = options.protocol.maxRounds ?? 2;
2079
2309
  let firstRoundContributions = [];
2080
2310
  let lastContributions = [];
2081
- const startedAtMs = nowMs$3();
2311
+ const startedAtMs = nowMs();
2082
2312
  let stopped = false;
2083
2313
  let termination;
2314
+ const wrapUpHint = createWrapUpHintController({
2315
+ protocol: options.protocol,
2316
+ tier: options.tier,
2317
+ ...options.budget ? { budget: options.budget } : {},
2318
+ ...options.terminate ? { terminate: options.terminate } : {},
2319
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}
2320
+ });
2321
+ warnOnProtocolTerminationMisconfiguration(options.protocol, options.terminate);
2084
2322
  const emit = (event) => {
2085
2323
  events.push(event);
2086
2324
  options.emit?.(event);
@@ -2134,13 +2372,21 @@ async function runBroadcast(options) {
2134
2372
  round,
2135
2373
  ...toolAvailability
2136
2374
  },
2137
- messages: [{
2375
+ messages: wrapUpHint.inject([{
2138
2376
  role: "system",
2139
2377
  content: buildSystemPrompt$3(agent)
2140
2378
  }, {
2141
2379
  role: "user",
2142
2380
  content: input
2143
- }]
2381
+ }], {
2382
+ runId,
2383
+ protocol: "broadcast",
2384
+ cost: totalCost,
2385
+ events,
2386
+ transcript,
2387
+ iteration: transcript.length,
2388
+ elapsedMs: elapsedMs(startedAtMs)
2389
+ })
2144
2390
  };
2145
2391
  const response = await generateModelTurn({
2146
2392
  model: options.model,
@@ -2149,7 +2395,7 @@ async function runBroadcast(options) {
2149
2395
  agent,
2150
2396
  input,
2151
2397
  emit,
2152
- callId: providerCallIdFor$2(runId, providerCalls.length + agentIndex + 1),
2398
+ callId: providerCallIdFor(runId, providerCalls.length + agentIndex + 1),
2153
2399
  onProviderCall(call) {
2154
2400
  providerCallSlots[agentIndex] = call;
2155
2401
  }
@@ -2305,16 +2551,17 @@ async function runBroadcast(options) {
2305
2551
  function stopIfNeeded() {
2306
2552
  throwIfAborted(options.signal, options.model.id);
2307
2553
  if (stopped || !options.terminate) return stopped;
2308
- const stopRecord = evaluateTerminationStop(options.terminate, {
2554
+ const stopRecord = evaluateTerminationStop(options.terminate, wrapUpHint.context({
2309
2555
  runId,
2310
2556
  protocol: "broadcast",
2311
- tier: options.tier,
2557
+ protocolConfig: options.protocol,
2558
+ protocolIteration: broadcastRoundsCompleted(events),
2312
2559
  cost: totalCost,
2313
2560
  events,
2314
2561
  transcript,
2315
2562
  iteration: transcript.length,
2316
- elapsedMs: elapsedMs$3(startedAtMs)
2317
- });
2563
+ elapsedMs: elapsedMs(startedAtMs)
2564
+ }));
2318
2565
  if (!stopRecord) return false;
2319
2566
  stopped = true;
2320
2567
  termination = stopRecord;
@@ -2329,13 +2576,16 @@ async function runBroadcast(options) {
2329
2576
  reason: record.budgetReason ?? "cost",
2330
2577
  cost: totalCost,
2331
2578
  iteration: transcript.length,
2332
- elapsedMs: elapsedMs$3(startedAtMs),
2579
+ elapsedMs: elapsedMs(startedAtMs),
2333
2580
  detail: record.detail ?? {}
2334
2581
  };
2335
2582
  emit(event);
2336
2583
  recordProtocolDecision(event, { transcriptEntryCount: transcript.length });
2337
2584
  }
2338
2585
  }
2586
+ function broadcastRoundsCompleted(events) {
2587
+ return events.filter((event) => event.type === "broadcast").length;
2588
+ }
2339
2589
  function buildSystemPrompt$3(agent) {
2340
2590
  const instruction = agent.instructions ? `\nInstructions: ${agent.instructions}` : "";
2341
2591
  return `You are ${agent.id}, acting as ${agent.role} in a Broadcast multi-agent protocol.${instruction}`;
@@ -2356,22 +2606,10 @@ function responseCost$3(response) {
2356
2606
  totalTokens: response.usage?.totalTokens ?? 0
2357
2607
  };
2358
2608
  }
2359
- function createRunId$3() {
2360
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
2361
- }
2362
- function nowMs$3() {
2363
- return globalThis.performance?.now() ?? Date.now();
2364
- }
2365
- function elapsedMs$3(startedAtMs) {
2366
- return Math.max(0, nowMs$3() - startedAtMs);
2367
- }
2368
- function providerCallIdFor$2(runId, oneBasedIndex) {
2369
- return `${runId}:provider-call:${oneBasedIndex}`;
2370
- }
2371
2609
  //#endregion
2372
2610
  //#region src/runtime/coordinator.ts
2373
2611
  async function runCoordinator(options) {
2374
- const runId = createRunId$2();
2612
+ const runId = createRunId();
2375
2613
  const events = [];
2376
2614
  const transcript = [];
2377
2615
  const protocolDecisions = [];
@@ -2380,9 +2618,17 @@ async function runCoordinator(options) {
2380
2618
  const maxTurns = options.protocol.maxTurns ?? options.agents.length;
2381
2619
  const activeAgents = options.agents.slice(0, maxTurns);
2382
2620
  const coordinator = activeAgents[0];
2383
- const startedAtMs = nowMs$2();
2621
+ const startedAtMs = nowMs();
2384
2622
  let stopped = false;
2385
2623
  let termination;
2624
+ const wrapUpHint = createWrapUpHintController({
2625
+ protocol: options.protocol,
2626
+ tier: options.tier,
2627
+ ...options.budget ? { budget: options.budget } : {},
2628
+ ...options.terminate ? { terminate: options.terminate } : {},
2629
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}
2630
+ });
2631
+ warnOnProtocolTerminationMisconfiguration(options.protocol, options.terminate);
2386
2632
  const emit = (event) => {
2387
2633
  events.push(event);
2388
2634
  options.emit?.(event);
@@ -2432,6 +2678,9 @@ async function runCoordinator(options) {
2432
2678
  providerCalls,
2433
2679
  toolExecutor,
2434
2680
  toolAvailability,
2681
+ events,
2682
+ startedAtMs,
2683
+ wrapUpHint,
2435
2684
  emit,
2436
2685
  recordProtocolDecision
2437
2686
  });
@@ -2448,11 +2697,16 @@ async function runCoordinator(options) {
2448
2697
  options,
2449
2698
  runId,
2450
2699
  turn: transcript.length + index + 1,
2451
- providerCallId: providerCallIdFor$1(runId, providerCalls.length + index + 1),
2700
+ providerCallId: providerCallIdFor(runId, providerCalls.length + index + 1),
2452
2701
  providerCallIndex: index,
2453
2702
  providerCallSlots,
2454
2703
  toolExecutor,
2455
2704
  toolAvailability,
2705
+ totalCost,
2706
+ events,
2707
+ transcript: planTranscript,
2708
+ startedAtMs,
2709
+ wrapUpHint,
2456
2710
  emit
2457
2711
  })));
2458
2712
  providerCalls.push(...providerCallSlots.filter((call) => call !== void 0));
@@ -2499,6 +2753,9 @@ async function runCoordinator(options) {
2499
2753
  providerCalls,
2500
2754
  toolExecutor,
2501
2755
  toolAvailability,
2756
+ events,
2757
+ startedAtMs,
2758
+ wrapUpHint,
2502
2759
  emit,
2503
2760
  recordProtocolDecision
2504
2761
  });
@@ -2579,16 +2836,17 @@ async function runCoordinator(options) {
2579
2836
  function stopIfNeeded() {
2580
2837
  throwIfAborted(options.signal, options.model.id);
2581
2838
  if (stopped || !options.terminate) return stopped;
2582
- const stopRecord = evaluateTerminationStop(options.terminate, {
2839
+ const stopRecord = evaluateTerminationStop(options.terminate, wrapUpHint.context({
2583
2840
  runId,
2584
2841
  protocol: "coordinator",
2585
- tier: options.tier,
2842
+ protocolConfig: options.protocol,
2843
+ protocolIteration: transcript.length,
2586
2844
  cost: totalCost,
2587
2845
  events,
2588
2846
  transcript,
2589
2847
  iteration: transcript.length,
2590
- elapsedMs: elapsedMs$2(startedAtMs)
2591
- });
2848
+ elapsedMs: elapsedMs(startedAtMs)
2849
+ }));
2592
2850
  if (!stopRecord) return false;
2593
2851
  stopped = true;
2594
2852
  termination = stopRecord;
@@ -2603,7 +2861,7 @@ async function runCoordinator(options) {
2603
2861
  reason: record.budgetReason ?? "cost",
2604
2862
  cost: totalCost,
2605
2863
  iteration: transcript.length,
2606
- elapsedMs: elapsedMs$2(startedAtMs),
2864
+ elapsedMs: elapsedMs(startedAtMs),
2607
2865
  detail: record.detail ?? {}
2608
2866
  };
2609
2867
  emit(event);
@@ -2625,13 +2883,21 @@ async function runCoordinatorTurn(turn) {
2625
2883
  phase: turn.phase,
2626
2884
  ...turn.toolAvailability
2627
2885
  },
2628
- messages: [{
2886
+ messages: turn.wrapUpHint.inject([{
2629
2887
  role: "system",
2630
2888
  content: buildSystemPrompt$2(turn.agent, turn.coordinator)
2631
2889
  }, {
2632
2890
  role: "user",
2633
2891
  content: turn.input
2634
- }]
2892
+ }], {
2893
+ runId: turn.runId,
2894
+ protocol: "coordinator",
2895
+ cost: turn.totalCost,
2896
+ events: turn.events,
2897
+ transcript: turn.transcript,
2898
+ iteration: turn.transcript.length,
2899
+ elapsedMs: elapsedMs(turn.startedAtMs)
2900
+ })
2635
2901
  };
2636
2902
  const response = await generateModelTurn({
2637
2903
  model: turn.options.model,
@@ -2698,13 +2964,21 @@ async function runCoordinatorWorkerTurn(turn) {
2698
2964
  phase: "worker",
2699
2965
  ...turn.toolAvailability
2700
2966
  },
2701
- messages: [{
2967
+ messages: turn.wrapUpHint.inject([{
2702
2968
  role: "system",
2703
2969
  content: buildSystemPrompt$2(turn.agent, turn.coordinator)
2704
2970
  }, {
2705
2971
  role: "user",
2706
2972
  content: turn.input
2707
- }]
2973
+ }], {
2974
+ runId: turn.runId,
2975
+ protocol: "coordinator",
2976
+ cost: turn.totalCost,
2977
+ events: turn.events,
2978
+ transcript: turn.transcript,
2979
+ iteration: turn.turn - 1,
2980
+ elapsedMs: elapsedMs(turn.startedAtMs)
2981
+ })
2708
2982
  };
2709
2983
  const response = await generateModelTurn({
2710
2984
  model: turn.options.model,
@@ -2761,22 +3035,10 @@ function responseCost$2(response) {
2761
3035
  totalTokens: response.usage?.totalTokens ?? 0
2762
3036
  };
2763
3037
  }
2764
- function createRunId$2() {
2765
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
2766
- }
2767
- function nowMs$2() {
2768
- return globalThis.performance?.now() ?? Date.now();
2769
- }
2770
- function elapsedMs$2(startedAtMs) {
2771
- return Math.max(0, nowMs$2() - startedAtMs);
2772
- }
2773
- function providerCallIdFor$1(runId, oneBasedIndex) {
2774
- return `${runId}:provider-call:${oneBasedIndex}`;
2775
- }
2776
3038
  //#endregion
2777
3039
  //#region src/runtime/sequential.ts
2778
3040
  async function runSequential(options) {
2779
- const runId = createRunId$1();
3041
+ const runId = createRunId();
2780
3042
  const events = [];
2781
3043
  const transcript = [];
2782
3044
  const protocolDecisions = [];
@@ -2784,9 +3046,17 @@ async function runSequential(options) {
2784
3046
  let totalCost = emptyCost();
2785
3047
  const maxTurns = options.protocol.maxTurns ?? options.agents.length;
2786
3048
  const activeAgents = options.agents.slice(0, maxTurns);
2787
- const startedAtMs = nowMs$1();
3049
+ const startedAtMs = nowMs();
2788
3050
  let stopped = false;
2789
3051
  let termination;
3052
+ const wrapUpHint = createWrapUpHintController({
3053
+ protocol: options.protocol,
3054
+ tier: options.tier,
3055
+ ...options.budget ? { budget: options.budget } : {},
3056
+ ...options.terminate ? { terminate: options.terminate } : {},
3057
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}
3058
+ });
3059
+ warnOnProtocolTerminationMisconfiguration(options.protocol, options.terminate);
2790
3060
  const emit = (event) => {
2791
3061
  events.push(event);
2792
3062
  options.emit?.(event);
@@ -2838,13 +3108,21 @@ async function runSequential(options) {
2838
3108
  turn,
2839
3109
  ...toolAvailability
2840
3110
  },
2841
- messages: [{
3111
+ messages: wrapUpHint.inject([{
2842
3112
  role: "system",
2843
3113
  content: buildSystemPrompt$1(agent)
2844
3114
  }, {
2845
3115
  role: "user",
2846
3116
  content: input
2847
- }]
3117
+ }], {
3118
+ runId,
3119
+ protocol: "sequential",
3120
+ cost: totalCost,
3121
+ events,
3122
+ transcript,
3123
+ iteration: transcript.length,
3124
+ elapsedMs: elapsedMs(startedAtMs)
3125
+ })
2848
3126
  };
2849
3127
  const response = await generateModelTurn({
2850
3128
  model: options.model,
@@ -2969,16 +3247,17 @@ async function runSequential(options) {
2969
3247
  function stopIfNeeded() {
2970
3248
  throwIfAborted(options.signal, options.model.id);
2971
3249
  if (stopped || !options.terminate) return stopped;
2972
- const stopRecord = evaluateTerminationStop(options.terminate, {
3250
+ const stopRecord = evaluateTerminationStop(options.terminate, wrapUpHint.context({
2973
3251
  runId,
2974
3252
  protocol: "sequential",
2975
- tier: options.tier,
3253
+ protocolConfig: options.protocol,
3254
+ protocolIteration: transcript.length,
2976
3255
  cost: totalCost,
2977
3256
  events,
2978
3257
  transcript,
2979
3258
  iteration: transcript.length,
2980
- elapsedMs: elapsedMs$1(startedAtMs)
2981
- });
3259
+ elapsedMs: elapsedMs(startedAtMs)
3260
+ }));
2982
3261
  if (!stopRecord) return false;
2983
3262
  stopped = true;
2984
3263
  termination = stopRecord;
@@ -2993,7 +3272,7 @@ async function runSequential(options) {
2993
3272
  reason: record.budgetReason ?? "cost",
2994
3273
  cost: totalCost,
2995
3274
  iteration: transcript.length,
2996
- elapsedMs: elapsedMs$1(startedAtMs),
3275
+ elapsedMs: elapsedMs(startedAtMs),
2997
3276
  detail: record.detail ?? {}
2998
3277
  };
2999
3278
  emit(event);
@@ -3016,15 +3295,6 @@ function responseCost$1(response) {
3016
3295
  totalTokens: response.usage?.totalTokens ?? 0
3017
3296
  };
3018
3297
  }
3019
- function createRunId$1() {
3020
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
3021
- }
3022
- function nowMs$1() {
3023
- return globalThis.performance?.now() ?? Date.now();
3024
- }
3025
- function elapsedMs$1(startedAtMs) {
3026
- return Math.max(0, nowMs$1() - startedAtMs);
3027
- }
3028
3298
  //#endregion
3029
3299
  //#region src/runtime/shared.ts
3030
3300
  async function runShared(options) {
@@ -3040,6 +3310,14 @@ async function runShared(options) {
3040
3310
  const startedAtMs = nowMs();
3041
3311
  let stopped = false;
3042
3312
  let termination;
3313
+ const wrapUpHint = createWrapUpHintController({
3314
+ protocol: options.protocol,
3315
+ tier: options.tier,
3316
+ ...options.budget ? { budget: options.budget } : {},
3317
+ ...options.terminate ? { terminate: options.terminate } : {},
3318
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}
3319
+ });
3320
+ warnOnProtocolTerminationMisconfiguration(options.protocol, options.terminate);
3043
3321
  const emit = (event) => {
3044
3322
  events.push(event);
3045
3323
  options.emit?.(event);
@@ -3092,13 +3370,21 @@ async function runShared(options) {
3092
3370
  turn,
3093
3371
  ...toolAvailability
3094
3372
  },
3095
- messages: [{
3373
+ messages: wrapUpHint.inject([{
3096
3374
  role: "system",
3097
3375
  content: buildSystemPrompt(agent)
3098
3376
  }, {
3099
3377
  role: "user",
3100
3378
  content: input
3101
- }]
3379
+ }], {
3380
+ runId,
3381
+ protocol: "shared",
3382
+ cost: totalCost,
3383
+ events,
3384
+ transcript,
3385
+ iteration: transcript.length,
3386
+ elapsedMs: elapsedMs(startedAtMs)
3387
+ })
3102
3388
  };
3103
3389
  const response = await generateModelTurn({
3104
3390
  model: options.model,
@@ -3235,16 +3521,17 @@ async function runShared(options) {
3235
3521
  function stopIfNeeded() {
3236
3522
  throwIfAborted(options.signal, options.model.id);
3237
3523
  if (stopped || !options.terminate) return stopped;
3238
- const stopRecord = evaluateTerminationStop(options.terminate, {
3524
+ const stopRecord = evaluateTerminationStop(options.terminate, wrapUpHint.context({
3239
3525
  runId,
3240
3526
  protocol: "shared",
3241
- tier: options.tier,
3527
+ protocolConfig: options.protocol,
3528
+ protocolIteration: transcript.length,
3242
3529
  cost: totalCost,
3243
3530
  events,
3244
3531
  transcript,
3245
3532
  iteration: transcript.length,
3246
3533
  elapsedMs: elapsedMs(startedAtMs)
3247
- });
3534
+ }));
3248
3535
  if (!stopRecord) return false;
3249
3536
  stopped = true;
3250
3537
  termination = stopRecord;
@@ -3284,18 +3571,6 @@ function responseCost(response) {
3284
3571
  totalTokens: response.usage?.totalTokens ?? 0
3285
3572
  };
3286
3573
  }
3287
- function createRunId() {
3288
- return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
3289
- }
3290
- function nowMs() {
3291
- return globalThis.performance?.now() ?? Date.now();
3292
- }
3293
- function elapsedMs(startedAtMs) {
3294
- return Math.max(0, nowMs() - startedAtMs);
3295
- }
3296
- function providerCallIdFor(runId, oneBasedIndex) {
3297
- return `${runId}:provider-call:${oneBasedIndex}`;
3298
- }
3299
3574
  //#endregion
3300
3575
  //#region src/runtime/engine.ts
3301
3576
  var defaultHighLevelProtocol = "sequential";
@@ -3333,6 +3608,7 @@ function createEngine(options) {
3333
3608
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3334
3609
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3335
3610
  ...terminate ? { terminate } : {},
3611
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3336
3612
  ...options.evaluate ? { evaluate: options.evaluate } : {}
3337
3613
  });
3338
3614
  },
@@ -3715,6 +3991,7 @@ function runProtocol(options) {
3715
3991
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3716
3992
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3717
3993
  ...options.terminate ? { terminate: options.terminate } : {},
3994
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3718
3995
  ...options.emit ? { emit: options.emit } : {}
3719
3996
  });
3720
3997
  case "broadcast": return runBroadcast({
@@ -3729,6 +4006,7 @@ function runProtocol(options) {
3729
4006
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3730
4007
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3731
4008
  ...options.terminate ? { terminate: options.terminate } : {},
4009
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3732
4010
  ...options.emit ? { emit: options.emit } : {}
3733
4011
  });
3734
4012
  case "coordinator": return runCoordinator({
@@ -3743,6 +4021,7 @@ function runProtocol(options) {
3743
4021
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3744
4022
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3745
4023
  ...options.terminate ? { terminate: options.terminate } : {},
4024
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3746
4025
  ...options.emit ? { emit: options.emit } : {}
3747
4026
  });
3748
4027
  case "shared": return runShared({
@@ -3757,6 +4036,7 @@ function runProtocol(options) {
3757
4036
  ...options.seed !== void 0 ? { seed: options.seed } : {},
3758
4037
  ...options.signal !== void 0 ? { signal: options.signal } : {},
3759
4038
  ...options.terminate ? { terminate: options.terminate } : {},
4039
+ ...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
3760
4040
  ...options.emit ? { emit: options.emit } : {}
3761
4041
  });
3762
4042
  }
@@ -4200,6 +4480,263 @@ function isJsonValue(value) {
4200
4480
  return false;
4201
4481
  }
4202
4482
  //#endregion
4203
- export { Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput };
4483
+ //#region src/runtime/logger.ts
4484
+ /** Logger that drops every call. The default when no logger is supplied. */
4485
+ var noopLogger = {
4486
+ debug() {},
4487
+ info() {},
4488
+ warn() {},
4489
+ error() {}
4490
+ };
4491
+ /**
4492
+ * Build a console-backed logger respecting a minimum level.
4493
+ *
4494
+ * The output format is JSON-on-one-line so it can be piped straight into log
4495
+ * collectors. Use `loggerFromEvents` to bridge it to a Dogpile stream handle.
4496
+ */
4497
+ function consoleLogger(options = {}) {
4498
+ const minLevel = options.level ?? "info";
4499
+ const allowed = (level) => LEVEL_ORDER[level] >= LEVEL_ORDER[minLevel];
4500
+ const emit = (level, message, fields) => {
4501
+ if (!allowed(level)) return;
4502
+ const payload = {
4503
+ level,
4504
+ message
4505
+ };
4506
+ if (fields !== void 0) payload.fields = fields;
4507
+ (level === "error" ? console.error : level === "warn" ? console.warn : console.log)(JSON.stringify(payload));
4508
+ };
4509
+ return {
4510
+ debug: (message, fields) => emit("debug", message, fields),
4511
+ info: (message, fields) => emit("info", message, fields),
4512
+ warn: (message, fields) => emit("warn", message, fields),
4513
+ error: (message, fields) => emit("error", message, fields)
4514
+ };
4515
+ }
4516
+ var LEVEL_ORDER = {
4517
+ debug: 0,
4518
+ info: 1,
4519
+ warn: 2,
4520
+ error: 3
4521
+ };
4522
+ /**
4523
+ * Bridge a `Logger` to a Dogpile stream handle by returning a
4524
+ * `StreamEventSubscriber`. Pass it to `handle.subscribe(...)`.
4525
+ *
4526
+ * Logger throws are caught and routed to `logger.error` so a misbehaving
4527
+ * logger can never crash an in-flight run.
4528
+ *
4529
+ * @example
4530
+ * ```ts
4531
+ * const handle = Dogpile.stream({ intent, model });
4532
+ * handle.subscribe(loggerFromEvents(consoleLogger({ level: "info" })));
4533
+ * const result = await handle.result;
4534
+ * ```
4535
+ */
4536
+ function loggerFromEvents(logger, options = {}) {
4537
+ const includeSet = options.include ? new Set(options.include) : void 0;
4538
+ return (event) => {
4539
+ const eventType = event.type;
4540
+ if (includeSet && !includeSet.has(eventType)) return;
4541
+ const level = options.levelFor?.(event) ?? defaultLevel(event);
4542
+ const message = describeEvent(event);
4543
+ const fields = summarizeEvent(event);
4544
+ try {
4545
+ logger[level](message, fields);
4546
+ } catch (cause) {
4547
+ try {
4548
+ logger.error("dogpile logger threw while handling event", {
4549
+ eventType,
4550
+ error: cause instanceof Error ? cause.message : String(cause)
4551
+ });
4552
+ } catch {}
4553
+ }
4554
+ };
4555
+ }
4556
+ function defaultLevel(event) {
4557
+ switch (event.type) {
4558
+ case "model-output-chunk": return "debug";
4559
+ case "budget-stop": return "warn";
4560
+ case "error": return "error";
4561
+ case "tool-result": return event.result?.type === "error" ? "warn" : "info";
4562
+ default: return "info";
4563
+ }
4564
+ }
4565
+ function describeEvent(event) {
4566
+ return `dogpile:${event.type}`;
4567
+ }
4568
+ function summarizeEvent(event) {
4569
+ const fields = { eventType: event.type };
4570
+ const at = event.at;
4571
+ if (typeof at === "string") fields.at = at;
4572
+ const runId = event.runId;
4573
+ if (typeof runId === "string") fields.runId = runId;
4574
+ const agentId = event.agentId;
4575
+ if (typeof agentId === "string") fields.agentId = agentId;
4576
+ const role = event.role;
4577
+ if (typeof role === "string") fields.role = role;
4578
+ return fields;
4579
+ }
4580
+ //#endregion
4581
+ //#region src/runtime/retry.ts
4582
+ /**
4583
+ * Default DogpileError codes that `withRetry` retries when no `retryOn`
4584
+ * predicate is supplied. These map to the transient provider failures listed
4585
+ * in `docs/developer-usage.md`.
4586
+ */
4587
+ var DEFAULT_RETRYABLE_DOGPILE_CODES = [
4588
+ "provider-rate-limited",
4589
+ "provider-timeout",
4590
+ "provider-unavailable"
4591
+ ];
4592
+ var DEFAULTS = {
4593
+ maxAttempts: 3,
4594
+ baseDelayMs: 250,
4595
+ maxDelayMs: 4e3,
4596
+ jitter: "full"
4597
+ };
4598
+ /**
4599
+ * Wrap a `ConfiguredModelProvider` with a retry policy. The wrapper:
4600
+ *
4601
+ * - Preserves the provider `id` so traces remain stable.
4602
+ * - Retries `generate()` calls when the policy says the error is retryable.
4603
+ * - Propagates `AbortSignal` cancellation immediately — never retries after
4604
+ * the caller cancels.
4605
+ * - Honors a `Retry-After`-style hint exposed via `error.detail.retryAfterMs`
4606
+ * when present and the policy did not provide its own `delayForError`.
4607
+ * - Forwards `stream()` calls through unchanged — streaming retries cannot be
4608
+ * safely automated because partial output may have already been observed.
4609
+ *
4610
+ * @example
4611
+ * ```ts
4612
+ * const robustProvider = withRetry(rawProvider, {
4613
+ * maxAttempts: 4,
4614
+ * baseDelayMs: 500,
4615
+ * onRetry: ({ attempt, delayMs, error }) => {
4616
+ * logger.warn("provider retry", { attempt, delayMs, error });
4617
+ * }
4618
+ * });
4619
+ * ```
4620
+ */
4621
+ function withRetry(provider, policy = {}) {
4622
+ const settings = {
4623
+ maxAttempts: policy.maxAttempts ?? DEFAULTS.maxAttempts,
4624
+ baseDelayMs: policy.baseDelayMs ?? DEFAULTS.baseDelayMs,
4625
+ maxDelayMs: policy.maxDelayMs ?? DEFAULTS.maxDelayMs,
4626
+ jitter: policy.jitter ?? DEFAULTS.jitter,
4627
+ retryOn: policy.retryOn ?? defaultRetryOn,
4628
+ random: policy.random ?? Math.random,
4629
+ sleep: policy.sleep ?? defaultSleep
4630
+ };
4631
+ if (settings.maxAttempts < 1) throw new DogpileError({
4632
+ code: "invalid-configuration",
4633
+ message: "withRetry: maxAttempts must be >= 1.",
4634
+ detail: { maxAttempts: settings.maxAttempts }
4635
+ });
4636
+ if (settings.baseDelayMs < 0 || settings.maxDelayMs < 0) throw new DogpileError({
4637
+ code: "invalid-configuration",
4638
+ message: "withRetry: delay fields must be non-negative.",
4639
+ detail: {
4640
+ baseDelayMs: settings.baseDelayMs,
4641
+ maxDelayMs: settings.maxDelayMs
4642
+ }
4643
+ });
4644
+ const wrapped = {
4645
+ id: provider.id,
4646
+ async generate(request) {
4647
+ let lastError;
4648
+ for (let attempt = 1; attempt <= settings.maxAttempts; attempt++) {
4649
+ if (request.signal?.aborted) throw abortReason(request.signal);
4650
+ try {
4651
+ return await provider.generate(request);
4652
+ } catch (error) {
4653
+ lastError = error;
4654
+ if (isAbortError(error) || request.signal?.aborted) throw error;
4655
+ if (attempt >= settings.maxAttempts || !settings.retryOn(error)) throw error;
4656
+ const delayMs = chooseDelay({
4657
+ attempt,
4658
+ error,
4659
+ settings,
4660
+ policy
4661
+ });
4662
+ policy.onRetry?.({
4663
+ attempt,
4664
+ maxAttempts: settings.maxAttempts,
4665
+ delayMs,
4666
+ error,
4667
+ providerId: provider.id
4668
+ });
4669
+ await settings.sleep(delayMs, request.signal);
4670
+ }
4671
+ }
4672
+ throw lastError ?? new DogpileError({
4673
+ code: "unknown",
4674
+ message: "withRetry: exhausted attempts without throwing or returning."
4675
+ });
4676
+ }
4677
+ };
4678
+ if (typeof provider.stream === "function") {
4679
+ const upstreamStream = provider.stream.bind(provider);
4680
+ wrapped.stream = (request) => upstreamStream(request);
4681
+ }
4682
+ return wrapped;
4683
+ }
4684
+ function chooseDelay(args) {
4685
+ const override = args.policy.delayForError?.(args.error) ?? retryAfterFromError(args.error);
4686
+ if (override !== void 0 && Number.isFinite(override) && override >= 0) return Math.min(args.settings.maxDelayMs, override);
4687
+ const exponential = args.settings.baseDelayMs * 2 ** (args.attempt - 1);
4688
+ const capped = Math.min(args.settings.maxDelayMs, exponential);
4689
+ if (args.settings.jitter === "none") return capped;
4690
+ return Math.floor(capped * args.settings.random());
4691
+ }
4692
+ function defaultRetryOn(error) {
4693
+ if (isAbortError(error)) return false;
4694
+ if (DogpileError.isInstance(error)) {
4695
+ if (error.code === "aborted" || error.code === "invalid-configuration") return false;
4696
+ return DEFAULT_RETRYABLE_DOGPILE_CODES.includes(error.code);
4697
+ }
4698
+ if (error instanceof TypeError) return true;
4699
+ return false;
4700
+ }
4701
+ function isAbortError(error) {
4702
+ if (DogpileError.isInstance(error) && error.code === "aborted") return true;
4703
+ if (typeof error === "object" && error !== null) {
4704
+ if (error.name === "AbortError") return true;
4705
+ }
4706
+ return false;
4707
+ }
4708
+ function abortReason(signal) {
4709
+ return signal.reason ?? new DogpileError({
4710
+ code: "aborted",
4711
+ message: "Request aborted."
4712
+ });
4713
+ }
4714
+ function retryAfterFromError(error) {
4715
+ if (!DogpileError.isInstance(error)) return void 0;
4716
+ const detail = error.detail;
4717
+ if (!detail || typeof detail !== "object") return void 0;
4718
+ const candidate = detail.retryAfterMs;
4719
+ if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0) return candidate;
4720
+ }
4721
+ function defaultSleep(ms, signal) {
4722
+ if (ms <= 0) return Promise.resolve();
4723
+ return new Promise((resolve, reject) => {
4724
+ if (signal?.aborted) {
4725
+ reject(abortReason(signal));
4726
+ return;
4727
+ }
4728
+ const timer = setTimeout(() => {
4729
+ signal?.removeEventListener("abort", onAbort);
4730
+ resolve();
4731
+ }, ms);
4732
+ const onAbort = () => {
4733
+ clearTimeout(timer);
4734
+ reject(abortReason(signal));
4735
+ };
4736
+ signal?.addEventListener("abort", onAbort, { once: true });
4737
+ });
4738
+ }
4739
+ //#endregion
4740
+ export { DEFAULT_RETRYABLE_DOGPILE_CODES, Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, consoleLogger, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, loggerFromEvents, noopLogger, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput, withRetry };
4204
4741
 
4205
4742
  //# sourceMappingURL=index.js.map