@maxanstey-meridian/tandem 0.1.5 → 0.2.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 (151) hide show
  1. package/README.md +5 -5
  2. package/dist/index.d.ts +86 -72
  3. package/dist/index.js +656 -894
  4. package/dist/runtime/darwin-arm64/Microsoft.JavaScript.NodeApi.Generator.dll +0 -0
  5. package/dist/runtime/darwin-arm64/Tandem.Advanced.dll +0 -0
  6. package/dist/runtime/darwin-arm64/Tandem.Ledger.dll +0 -0
  7. package/dist/runtime/{Tandem.NodeApiSpike.Bridge.cjs → darwin-arm64/Tandem.NodeApiSpike.Bridge.cjs} +1 -1
  8. package/dist/runtime/{Tandem.NodeApiSpike.Bridge.d.ts → darwin-arm64/Tandem.NodeApiSpike.Bridge.d.ts} +1 -1
  9. package/dist/runtime/{Tandem.NodeApiSpike.Bridge.deps.json → darwin-arm64/Tandem.NodeApiSpike.Bridge.deps.json} +55 -55
  10. package/dist/runtime/{Tandem.NodeApiSpike.Bridge.dll → darwin-arm64/Tandem.NodeApiSpike.Bridge.dll} +0 -0
  11. package/dist/runtime/{Tandem.NodeApiSpike.Bridge.mjs → darwin-arm64/Tandem.NodeApiSpike.Bridge.mjs} +1 -1
  12. package/dist/runtime/{Tandem.OpenAICompatible.dll → darwin-arm64/Tandem.OpenAICompatible.dll} +0 -0
  13. package/dist/runtime/{Tandem.Terminal.dll → darwin-arm64/Tandem.Terminal.dll} +0 -0
  14. package/dist/runtime/{Tandem.dll → darwin-arm64/Tandem.dll} +0 -0
  15. package/dist/runtime/linux-x64/FluentValidation.dll +0 -0
  16. package/dist/runtime/linux-x64/Google.Protobuf.dll +0 -0
  17. package/dist/runtime/linux-x64/Humanizer.dll +0 -0
  18. package/dist/runtime/linux-x64/Json.More.dll +0 -0
  19. package/dist/runtime/linux-x64/JsonPointer.Net.dll +0 -0
  20. package/dist/runtime/linux-x64/JsonSchema.Net.Generation.dll +0 -0
  21. package/dist/runtime/linux-x64/JsonSchema.Net.dll +0 -0
  22. package/dist/runtime/linux-x64/Microsoft.Agents.AI.Abstractions.dll +0 -0
  23. package/dist/runtime/linux-x64/Microsoft.Agents.AI.Harness.dll +0 -0
  24. package/dist/runtime/linux-x64/Microsoft.Agents.AI.Tools.Shell.dll +0 -0
  25. package/dist/runtime/linux-x64/Microsoft.Agents.AI.Workflows.dll +0 -0
  26. package/dist/runtime/linux-x64/Microsoft.Agents.AI.dll +0 -0
  27. package/dist/runtime/linux-x64/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  28. package/dist/runtime/linux-x64/Microsoft.CodeAnalysis.dll +0 -0
  29. package/dist/runtime/linux-x64/Microsoft.Data.Sqlite.dll +0 -0
  30. package/dist/runtime/linux-x64/Microsoft.Extensions.AI.Abstractions.dll +0 -0
  31. package/dist/runtime/linux-x64/Microsoft.Extensions.AI.Evaluation.dll +0 -0
  32. package/dist/runtime/linux-x64/Microsoft.Extensions.AI.OpenAI.dll +0 -0
  33. package/dist/runtime/linux-x64/Microsoft.Extensions.AI.dll +0 -0
  34. package/dist/runtime/linux-x64/Microsoft.Extensions.Caching.Abstractions.dll +0 -0
  35. package/dist/runtime/linux-x64/Microsoft.Extensions.Compliance.Abstractions.dll +0 -0
  36. package/dist/runtime/linux-x64/Microsoft.Extensions.Configuration.Abstractions.dll +0 -0
  37. package/dist/runtime/linux-x64/Microsoft.Extensions.DependencyInjection.Abstractions.dll +0 -0
  38. package/dist/runtime/linux-x64/Microsoft.Extensions.Diagnostics.Abstractions.dll +0 -0
  39. package/dist/runtime/linux-x64/Microsoft.Extensions.FileProviders.Abstractions.dll +0 -0
  40. package/dist/runtime/linux-x64/Microsoft.Extensions.FileSystemGlobbing.dll +0 -0
  41. package/dist/runtime/linux-x64/Microsoft.Extensions.Hosting.Abstractions.dll +0 -0
  42. package/dist/runtime/linux-x64/Microsoft.Extensions.Logging.Abstractions.dll +0 -0
  43. package/dist/runtime/linux-x64/Microsoft.Extensions.ObjectPool.dll +0 -0
  44. package/dist/runtime/linux-x64/Microsoft.Extensions.Options.dll +0 -0
  45. package/dist/runtime/linux-x64/Microsoft.Extensions.Primitives.dll +0 -0
  46. package/dist/runtime/linux-x64/Microsoft.Extensions.VectorData.Abstractions.dll +0 -0
  47. package/dist/runtime/linux-x64/Microsoft.JavaScript.NodeApi.Generator.dll +0 -0
  48. package/dist/runtime/linux-x64/Microsoft.JavaScript.NodeApi.dll +0 -0
  49. package/dist/runtime/linux-x64/Microsoft.ML.Tokenizers.dll +0 -0
  50. package/dist/runtime/linux-x64/OpenAI.dll +0 -0
  51. package/dist/runtime/linux-x64/OpenTelemetry.Api.dll +0 -0
  52. package/dist/runtime/linux-x64/SQLitePCLRaw.batteries_v2.dll +0 -0
  53. package/dist/runtime/linux-x64/SQLitePCLRaw.core.dll +0 -0
  54. package/dist/runtime/linux-x64/SQLitePCLRaw.provider.e_sqlite3.dll +0 -0
  55. package/dist/runtime/linux-x64/Spectre.Console.Ansi.dll +0 -0
  56. package/dist/runtime/linux-x64/Spectre.Console.dll +0 -0
  57. package/dist/runtime/linux-x64/System.ClientModel.dll +0 -0
  58. package/dist/runtime/linux-x64/System.Memory.Data.dll +0 -0
  59. package/dist/runtime/linux-x64/System.Numerics.Tensors.dll +0 -0
  60. package/dist/runtime/linux-x64/System.Reflection.MetadataLoadContext.dll +0 -0
  61. package/dist/runtime/{Tandem.Advanced.dll → linux-x64/Tandem.Advanced.dll} +0 -0
  62. package/dist/runtime/{Tandem.Ledger.dll → linux-x64/Tandem.Ledger.dll} +0 -0
  63. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.cjs +26 -0
  64. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.d.ts +30 -0
  65. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.deps.json +1157 -0
  66. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.dll +0 -0
  67. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.mjs +31 -0
  68. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.runtimeconfig.json +13 -0
  69. package/dist/runtime/linux-x64/Tandem.OpenAICompatible.dll +0 -0
  70. package/dist/runtime/linux-x64/Tandem.Terminal.dll +0 -0
  71. package/dist/runtime/linux-x64/Tandem.dll +0 -0
  72. package/dist/runtime/linux-x64/Tavily.dll +0 -0
  73. package/dist/runtime/linux-x64/import.cjs +3 -0
  74. package/dist/runtime/linux-x64/libe_sqlite3.so +0 -0
  75. package/dist/runtime/loader.mjs +3 -2
  76. package/package.json +3 -1
  77. /package/dist/runtime/{FluentValidation.dll → darwin-arm64/FluentValidation.dll} +0 -0
  78. /package/dist/runtime/{Google.Protobuf.dll → darwin-arm64/Google.Protobuf.dll} +0 -0
  79. /package/dist/runtime/{Humanizer.dll → darwin-arm64/Humanizer.dll} +0 -0
  80. /package/dist/runtime/{Json.More.dll → darwin-arm64/Json.More.dll} +0 -0
  81. /package/dist/runtime/{JsonPointer.Net.dll → darwin-arm64/JsonPointer.Net.dll} +0 -0
  82. /package/dist/runtime/{JsonSchema.Net.Generation.dll → darwin-arm64/JsonSchema.Net.Generation.dll} +0 -0
  83. /package/dist/runtime/{JsonSchema.Net.dll → darwin-arm64/JsonSchema.Net.dll} +0 -0
  84. /package/dist/runtime/{Microsoft.Agents.AI.Abstractions.dll → darwin-arm64/Microsoft.Agents.AI.Abstractions.dll} +0 -0
  85. /package/dist/runtime/{Microsoft.Agents.AI.Harness.dll → darwin-arm64/Microsoft.Agents.AI.Harness.dll} +0 -0
  86. /package/dist/runtime/{Microsoft.Agents.AI.Tools.Shell.dll → darwin-arm64/Microsoft.Agents.AI.Tools.Shell.dll} +0 -0
  87. /package/dist/runtime/{Microsoft.Agents.AI.Workflows.dll → darwin-arm64/Microsoft.Agents.AI.Workflows.dll} +0 -0
  88. /package/dist/runtime/{Microsoft.Agents.AI.dll → darwin-arm64/Microsoft.Agents.AI.dll} +0 -0
  89. /package/dist/runtime/{Microsoft.CodeAnalysis.CSharp.dll → darwin-arm64/Microsoft.CodeAnalysis.CSharp.dll} +0 -0
  90. /package/dist/runtime/{Microsoft.CodeAnalysis.dll → darwin-arm64/Microsoft.CodeAnalysis.dll} +0 -0
  91. /package/dist/runtime/{Microsoft.Data.Sqlite.dll → darwin-arm64/Microsoft.Data.Sqlite.dll} +0 -0
  92. /package/dist/runtime/{Microsoft.Extensions.AI.Abstractions.dll → darwin-arm64/Microsoft.Extensions.AI.Abstractions.dll} +0 -0
  93. /package/dist/runtime/{Microsoft.Extensions.AI.Evaluation.dll → darwin-arm64/Microsoft.Extensions.AI.Evaluation.dll} +0 -0
  94. /package/dist/runtime/{Microsoft.Extensions.AI.OpenAI.dll → darwin-arm64/Microsoft.Extensions.AI.OpenAI.dll} +0 -0
  95. /package/dist/runtime/{Microsoft.Extensions.AI.dll → darwin-arm64/Microsoft.Extensions.AI.dll} +0 -0
  96. /package/dist/runtime/{Microsoft.Extensions.Caching.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Caching.Abstractions.dll} +0 -0
  97. /package/dist/runtime/{Microsoft.Extensions.Compliance.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Compliance.Abstractions.dll} +0 -0
  98. /package/dist/runtime/{Microsoft.Extensions.Configuration.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Configuration.Abstractions.dll} +0 -0
  99. /package/dist/runtime/{Microsoft.Extensions.DependencyInjection.Abstractions.dll → darwin-arm64/Microsoft.Extensions.DependencyInjection.Abstractions.dll} +0 -0
  100. /package/dist/runtime/{Microsoft.Extensions.Diagnostics.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Diagnostics.Abstractions.dll} +0 -0
  101. /package/dist/runtime/{Microsoft.Extensions.FileProviders.Abstractions.dll → darwin-arm64/Microsoft.Extensions.FileProviders.Abstractions.dll} +0 -0
  102. /package/dist/runtime/{Microsoft.Extensions.FileSystemGlobbing.dll → darwin-arm64/Microsoft.Extensions.FileSystemGlobbing.dll} +0 -0
  103. /package/dist/runtime/{Microsoft.Extensions.Hosting.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Hosting.Abstractions.dll} +0 -0
  104. /package/dist/runtime/{Microsoft.Extensions.Logging.Abstractions.dll → darwin-arm64/Microsoft.Extensions.Logging.Abstractions.dll} +0 -0
  105. /package/dist/runtime/{Microsoft.Extensions.ObjectPool.dll → darwin-arm64/Microsoft.Extensions.ObjectPool.dll} +0 -0
  106. /package/dist/runtime/{Microsoft.Extensions.Options.dll → darwin-arm64/Microsoft.Extensions.Options.dll} +0 -0
  107. /package/dist/runtime/{Microsoft.Extensions.Primitives.dll → darwin-arm64/Microsoft.Extensions.Primitives.dll} +0 -0
  108. /package/dist/runtime/{Microsoft.Extensions.VectorData.Abstractions.dll → darwin-arm64/Microsoft.Extensions.VectorData.Abstractions.dll} +0 -0
  109. /package/dist/runtime/{Microsoft.JavaScript.NodeApi.dll → darwin-arm64/Microsoft.JavaScript.NodeApi.dll} +0 -0
  110. /package/dist/runtime/{Microsoft.ML.Tokenizers.dll → darwin-arm64/Microsoft.ML.Tokenizers.dll} +0 -0
  111. /package/dist/runtime/{OpenAI.dll → darwin-arm64/OpenAI.dll} +0 -0
  112. /package/dist/runtime/{OpenTelemetry.Api.dll → darwin-arm64/OpenTelemetry.Api.dll} +0 -0
  113. /package/dist/runtime/{SQLitePCLRaw.batteries_v2.dll → darwin-arm64/SQLitePCLRaw.batteries_v2.dll} +0 -0
  114. /package/dist/runtime/{SQLitePCLRaw.core.dll → darwin-arm64/SQLitePCLRaw.core.dll} +0 -0
  115. /package/dist/runtime/{SQLitePCLRaw.provider.e_sqlite3.dll → darwin-arm64/SQLitePCLRaw.provider.e_sqlite3.dll} +0 -0
  116. /package/dist/runtime/{Spectre.Console.Ansi.dll → darwin-arm64/Spectre.Console.Ansi.dll} +0 -0
  117. /package/dist/runtime/{Spectre.Console.dll → darwin-arm64/Spectre.Console.dll} +0 -0
  118. /package/dist/runtime/{System.ClientModel.dll → darwin-arm64/System.ClientModel.dll} +0 -0
  119. /package/dist/runtime/{System.Memory.Data.dll → darwin-arm64/System.Memory.Data.dll} +0 -0
  120. /package/dist/runtime/{System.Numerics.Tensors.dll → darwin-arm64/System.Numerics.Tensors.dll} +0 -0
  121. /package/dist/runtime/{System.Reflection.MetadataLoadContext.dll → darwin-arm64/System.Reflection.MetadataLoadContext.dll} +0 -0
  122. /package/dist/runtime/{Tandem.NodeApiSpike.Bridge.runtimeconfig.json → darwin-arm64/Tandem.NodeApiSpike.Bridge.runtimeconfig.json} +0 -0
  123. /package/dist/runtime/{Tavily.dll → darwin-arm64/Tavily.dll} +0 -0
  124. /package/dist/runtime/{cs → darwin-arm64/cs}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  125. /package/dist/runtime/{cs → darwin-arm64/cs}/Microsoft.CodeAnalysis.resources.dll +0 -0
  126. /package/dist/runtime/{de → darwin-arm64/de}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  127. /package/dist/runtime/{de → darwin-arm64/de}/Microsoft.CodeAnalysis.resources.dll +0 -0
  128. /package/dist/runtime/{es → darwin-arm64/es}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  129. /package/dist/runtime/{es → darwin-arm64/es}/Microsoft.CodeAnalysis.resources.dll +0 -0
  130. /package/dist/runtime/{fr → darwin-arm64/fr}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  131. /package/dist/runtime/{fr → darwin-arm64/fr}/Microsoft.CodeAnalysis.resources.dll +0 -0
  132. /package/dist/runtime/{import.cjs → darwin-arm64/import.cjs} +0 -0
  133. /package/dist/runtime/{it → darwin-arm64/it}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  134. /package/dist/runtime/{it → darwin-arm64/it}/Microsoft.CodeAnalysis.resources.dll +0 -0
  135. /package/dist/runtime/{ja → darwin-arm64/ja}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  136. /package/dist/runtime/{ja → darwin-arm64/ja}/Microsoft.CodeAnalysis.resources.dll +0 -0
  137. /package/dist/runtime/{ko → darwin-arm64/ko}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  138. /package/dist/runtime/{ko → darwin-arm64/ko}/Microsoft.CodeAnalysis.resources.dll +0 -0
  139. /package/dist/runtime/{libe_sqlite3.dylib → darwin-arm64/libe_sqlite3.dylib} +0 -0
  140. /package/dist/runtime/{pl → darwin-arm64/pl}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  141. /package/dist/runtime/{pl → darwin-arm64/pl}/Microsoft.CodeAnalysis.resources.dll +0 -0
  142. /package/dist/runtime/{pt-BR → darwin-arm64/pt-BR}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  143. /package/dist/runtime/{pt-BR → darwin-arm64/pt-BR}/Microsoft.CodeAnalysis.resources.dll +0 -0
  144. /package/dist/runtime/{ru → darwin-arm64/ru}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  145. /package/dist/runtime/{ru → darwin-arm64/ru}/Microsoft.CodeAnalysis.resources.dll +0 -0
  146. /package/dist/runtime/{tr → darwin-arm64/tr}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  147. /package/dist/runtime/{tr → darwin-arm64/tr}/Microsoft.CodeAnalysis.resources.dll +0 -0
  148. /package/dist/runtime/{zh-Hans → darwin-arm64/zh-Hans}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  149. /package/dist/runtime/{zh-Hans → darwin-arm64/zh-Hans}/Microsoft.CodeAnalysis.resources.dll +0 -0
  150. /package/dist/runtime/{zh-Hant → darwin-arm64/zh-Hant}/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  151. /package/dist/runtime/{zh-Hant → darwin-arm64/zh-Hant}/Microsoft.CodeAnalysis.resources.dll +0 -0
package/dist/index.js CHANGED
@@ -38,8 +38,10 @@ export class ContractValidationError extends TandemError {
38
38
  this.problems = problems;
39
39
  }
40
40
  }
41
- function callbackError(error) {
42
- return error instanceof ContractValidationError
41
+ const callbackSucceeded = (value) => JSON.stringify({ succeeded: true, value });
42
+ const callbackFailed = (error) => JSON.stringify({
43
+ succeeded: false,
44
+ error: error instanceof ContractValidationError
43
45
  ? {
44
46
  name: error.name,
45
47
  message: error.message,
@@ -49,94 +51,80 @@ function callbackError(error) {
49
51
  : {
50
52
  name: error instanceof Error ? error.name : "Error",
51
53
  message: error instanceof Error ? error.message : String(error),
52
- };
53
- }
54
+ },
55
+ });
56
+ /** Synchronous callbacks cannot observe cancellation; they receive a signal that never aborts. */
57
+ const neverAborted = new AbortController().signal;
54
58
  class CallbackRegistry {
55
- #sync = new Map();
56
- #async = new Map();
59
+ #callbacks = new Map();
57
60
  #next = 0;
58
61
  #disposed = false;
59
62
  registerSync(callback) {
60
- const id = this.#allocate();
61
- this.#sync.set(id, callback);
62
- return id;
63
+ return this.#register(callback);
63
64
  }
64
65
  registerAsync(callback) {
65
- const id = this.#allocate();
66
- this.#async.set(id, callback);
67
- return id;
66
+ return this.#register(callback);
68
67
  }
69
- invokeSync(id, state, input) {
68
+ /** Never throws: failures travel back to the bridge as an encoded `CallbackResult`. */
69
+ invoke(id, state, input, signal) {
70
70
  try {
71
- const callback = this.#sync.get(id);
71
+ const callback = this.#callbacks.get(id);
72
72
  if (!callback) {
73
73
  throw new Error(`Unknown internal callback '${id}'.`);
74
74
  }
75
- return JSON.stringify({
76
- succeeded: true,
77
- value: callback(state, input),
78
- });
79
- }
80
- catch (error) {
81
- return JSON.stringify({
82
- succeeded: false,
83
- error: callbackError(error),
84
- });
85
- }
86
- }
87
- async invokeAsync(id, state, input, signal) {
88
- try {
89
- const callback = this.#async.get(id);
90
- if (!callback) {
91
- throw new Error(`Unknown internal async callback '${id}'.`);
92
- }
93
- return JSON.stringify({
94
- succeeded: true,
95
- value: await callback(state, input, signal),
96
- });
75
+ const value = callback(state, input, signal);
76
+ return typeof value === "string"
77
+ ? callbackSucceeded(value)
78
+ : value.then(callbackSucceeded, callbackFailed);
97
79
  }
98
80
  catch (error) {
99
- return JSON.stringify({
100
- succeeded: false,
101
- error: callbackError(error),
102
- });
81
+ return callbackFailed(error);
103
82
  }
104
83
  }
105
84
  dispose() {
106
85
  this.#disposed = true;
107
- this.#sync.clear();
108
- this.#async.clear();
86
+ this.#callbacks.clear();
109
87
  }
110
- #allocate() {
88
+ #register(callback) {
111
89
  if (this.#disposed) {
112
90
  throw new Error("Callback registry has been disposed.");
113
91
  }
114
- return `c${this.#next++}`;
92
+ const id = `c${this.#next++}`;
93
+ this.#callbacks.set(id, callback);
94
+ return id;
115
95
  }
116
96
  }
97
+ // The bridge surfaces JS callback errors and .NET cancellations only as exception message text,
98
+ // so these markers are how a failure's origin is recovered (until the bridge returns structured
99
+ // errors).
100
+ const CALLBACK_CONTRACT_MARKER = "TANDEM_CALLBACK_CONTRACT:";
101
+ const CALLBACK_FAILURE_MARKER = "JavaScript callback failed:";
102
+ const BRIDGE_CANCELLATION_MESSAGE = /\boperation was (?:cancell?ed|aborted)\b/i;
103
+ const callbackFailureSchema = z.object({
104
+ boundary: z.string(),
105
+ problems: z.array(z.object({ path: z.string(), message: z.string() })),
106
+ });
117
107
  function callbackContractFailure(error) {
118
- const marker = "TANDEM_CALLBACK_CONTRACT:";
119
108
  const message = error instanceof Error ? error.message : String(error);
120
- const start = message.indexOf(marker);
109
+ const start = message.indexOf(CALLBACK_CONTRACT_MARKER);
121
110
  if (start < 0) {
122
111
  return null;
123
112
  }
124
113
  try {
125
- return JSON.parse(message.slice(start + marker.length));
114
+ const failure = callbackFailureSchema.safeParse(JSON.parse(message.slice(start + CALLBACK_CONTRACT_MARKER.length)));
115
+ return failure.success ? failure.data : null;
126
116
  }
127
117
  catch {
128
118
  return null;
129
119
  }
130
120
  }
131
- function isCancellationError(error, signalAborted) {
132
- if (!(error instanceof Error)) {
133
- return false;
134
- }
135
- if (error.message.includes("JavaScript callback failed:") && !signalAborted) {
136
- return false;
121
+ function isCancellation(error, signal) {
122
+ if (signal?.aborted) {
123
+ return true;
137
124
  }
138
- return (error.name === "AbortError" ||
139
- /\b(?:operation was cancel(?:l)?ed|operation was aborted|this operation was aborted)\b/i.test(error.message));
125
+ return (error instanceof Error &&
126
+ !error.message.includes(CALLBACK_FAILURE_MARKER) &&
127
+ (error.name === "AbortError" || BRIDGE_CANCELLATION_MESSAGE.test(error.message)));
140
128
  }
141
129
  function path(parts) {
142
130
  return parts.length === 0
@@ -176,43 +164,24 @@ function parse(schema, value, boundary) {
176
164
  }
177
165
  return result;
178
166
  }
167
+ /**
168
+ * The walker is the whole losslessness check: a stringify/parse round-trip compares only enumerable
169
+ * data, so it misses accessors and non-enumerable properties, and it cannot name the failing path.
170
+ */
179
171
  function serializeBoundary(schema, value, boundary) {
180
172
  const parsed = parse(schema, value, boundary);
181
173
  const problem = jsonValueProblem(parsed, "$", new WeakSet());
182
174
  if (problem) {
183
175
  throw new ContractValidationError(boundary, [problem]);
184
176
  }
185
- let json;
186
- try {
187
- json = JSON.stringify(parsed);
188
- }
189
- catch (error) {
190
- throw new ContractValidationError(boundary, [
191
- {
192
- path: "$",
193
- message: `Value is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,
194
- },
195
- ]);
196
- }
197
- if (json === undefined) {
198
- throw new ContractValidationError(boundary, [
199
- { path: "$", message: "Top-level undefined is not JSON-serializable." },
200
- ]);
201
- }
202
- const roundTripped = JSON.parse(json);
203
- if (!isDeepStrictEqual(roundTripped, parsed)) {
204
- throw new ContractValidationError(boundary, [
205
- { path: "$", message: "Value is not losslessly JSON-serializable." },
206
- ]);
207
- }
208
- return json;
177
+ return JSON.stringify(parsed);
209
178
  }
210
179
  function jsonValueProblem(value, valuePath, seen) {
211
180
  if (value === undefined) {
212
181
  return { path: valuePath, message: "undefined is not JSON-serializable." };
213
182
  }
214
- if (typeof value === "number" && !Number.isFinite(value)) {
215
- return { path: valuePath, message: "Non-finite numbers are not JSON-serializable." };
183
+ if (typeof value === "number" && (!Number.isFinite(value) || Object.is(value, -0))) {
184
+ return { path: valuePath, message: "Non-finite numbers and -0 are not JSON-serializable." };
216
185
  }
217
186
  if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") {
218
187
  return { path: valuePath, message: `${typeof value} values are not JSON-serializable.` };
@@ -312,164 +281,133 @@ function inputJsonSchema(schema, boundary) {
312
281
  ]);
313
282
  }
314
283
  }
315
- class NodeImplementation {
316
- id;
317
- persist;
318
- [participantBrand];
319
- constructor(id, persist) {
320
- this.id = id;
321
- this.persist = persist;
322
- }
323
- }
324
- class StageImplementation extends NodeImplementation {
325
- execute;
326
- kind = "stage";
327
- constructor(id, persist, execute) {
328
- super(id, persist);
329
- this.execute = execute;
330
- }
331
- }
284
+ const positiveInt32 = z.int32({ error: "must be a positive 32-bit integer" }).min(1);
285
+ const nonBlankString = z
286
+ .string({ error: "must be a non-blank string" })
287
+ .refine((value) => value.trim().length > 0, { error: "must be a non-blank string" });
288
+ const hasUnique = (items, key) => new Set(items.map(key)).size === items.length;
289
+ /** The participant brand only pins `TState` for the type checker; `kind` identifies records at runtime. */
290
+ const keepState = (state) => state;
332
291
  export function stage(definition) {
333
- return new StageImplementation(definition.id, definition.persist, definition.execute);
292
+ const record = {
293
+ ...definition,
294
+ [participantBrand]: keepState,
295
+ kind: "stage",
296
+ };
297
+ return Object.freeze(record);
334
298
  }
335
299
  const taskAgentBrand = Symbol("taskAgent");
336
- class TaskAgentImplementation {
337
- id;
338
- input;
339
- result;
340
- [taskAgentBrand] = true;
341
- state;
342
- participant;
343
- constructor(id, input, result, definition) {
344
- this.id = id;
345
- this.input = input;
346
- this.result = result;
347
- this.state = z.object({ input, output: z.object({ value: result }).nullable() });
348
- this.participant = agent({
300
+ export function taskAgent(definition) {
301
+ const { validateFor } = definition.output;
302
+ const record = {
303
+ [taskAgentBrand]: true,
304
+ id: definition.id,
305
+ input: definition.input,
306
+ result: definition.result,
307
+ state: z.object({
308
+ input: definition.input,
309
+ output: z.object({ value: definition.result }).nullable(),
310
+ }),
311
+ participant: agent({
349
312
  ...definition,
350
313
  message: (state) => definition.message(state.input),
351
314
  output: {
352
315
  ...definition.output,
353
- validateFor: definition.output.validateFor
354
- ? (state, value) => definition.output.validateFor(state.input, value)
355
- : undefined,
356
- apply: (state, value) => ({
357
- ...state,
358
- output: { value },
359
- }),
316
+ validateFor: validateFor ? (state, value) => validateFor(state.input, value) : undefined,
317
+ apply: (state, value) => ({ ...state, output: { value } }),
360
318
  },
361
- });
362
- }
363
- }
364
- export function taskAgent(definition) {
365
- return new TaskAgentImplementation(definition.id, definition.input, definition.result, definition);
319
+ }),
320
+ };
321
+ return Object.freeze(record);
366
322
  }
367
- class CollectionImplementation extends NodeImplementation {
368
- definition;
369
- kind = "collection";
370
- constructor(definition) {
371
- super(definition.id, definition.persist);
372
- this.definition = definition;
323
+ /** Task agents are only created by `taskAgent()`, whose record is the returned object. */
324
+ function taskAgentRecord(reference) {
325
+ if (!("participant" in reference)) {
326
+ throw new TandemError("Use taskAgent to declare collection agents.");
373
327
  }
328
+ return reference;
374
329
  }
330
+ const collectionOptionsSchema = z.object({
331
+ max: positiveInt32,
332
+ agents: z
333
+ .array(z.object({ id: z.string() }))
334
+ .refine((agents) => hasUnique(agents, (agent) => agent.id), { error: "must have unique IDs" }),
335
+ });
375
336
  export function collection(definition) {
376
- if (!Number.isInteger(definition.max) || definition.max < 1 || definition.max > 2147483647) {
377
- throw new TandemError("Collection max must be a positive 32-bit integer.");
378
- }
379
- if (new Set(definition.agents.map((agent) => agent.id)).size !== definition.agents.length) {
380
- throw new TandemError("Collection agent IDs must be unique.");
381
- }
382
- return new CollectionImplementation(definition);
383
- }
384
- class InteractionImplementation extends NodeImplementation {
385
- requestSchema;
386
- responseSchema;
387
- request;
388
- apply;
389
- kind = "interaction";
390
- requestType;
391
- responseType;
392
- constructor(id, persist, requestSchema, responseSchema, request, apply) {
393
- super(id, persist);
394
- this.requestSchema = requestSchema;
395
- this.responseSchema = responseSchema;
396
- this.request = request;
397
- this.apply = apply;
398
- }
337
+ parseDefinition(collectionOptionsSchema, definition, `Collection '${definition.id}'`);
338
+ const record = {
339
+ ...definition,
340
+ [participantBrand]: keepState,
341
+ kind: "collection",
342
+ };
343
+ return Object.freeze(record);
399
344
  }
400
345
  export function interaction(definition) {
401
- return new InteractionImplementation(definition.id, definition.persist, definition.requestSchema, definition.responseSchema, definition.request, definition.apply);
402
- }
403
- class InteractionHandlersImplementation {
404
- [interactionHandlersBrand] = true;
405
- entries = [];
406
- #interactions = new Set();
407
- handle(interaction, handler) {
408
- const opaque = interaction;
409
- if (this.#interactions.has(opaque)) {
410
- throw new TandemError(`Interaction '${interaction.id}' already has a handler.`);
411
- }
412
- this.#interactions.add(opaque);
413
- this.entries.push({
414
- interaction: opaque,
415
- handle: handler,
416
- });
417
- return this;
418
- }
346
+ const record = {
347
+ ...definition,
348
+ [participantBrand]: keepState,
349
+ kind: "interaction",
350
+ };
351
+ return Object.freeze(record);
419
352
  }
353
+ const registeredHandlers = new WeakMap();
420
354
  export function interactions() {
421
- return new InteractionHandlersImplementation();
355
+ const entries = [];
356
+ const handlers = {
357
+ [interactionHandlersBrand]: true,
358
+ handle(interaction, handler) {
359
+ if (entries.some((entry) => entry.interaction === interaction)) {
360
+ throw new TandemError(`Interaction '${interaction.id}' already has a handler.`);
361
+ }
362
+ entries.push({
363
+ // An interaction is the record `interaction()` built; its types are erased for storage.
364
+ interaction: interaction,
365
+ handle: handler,
366
+ });
367
+ return handlers;
368
+ },
369
+ };
370
+ registeredHandlers.set(handlers, entries);
371
+ return Object.freeze(handlers);
422
372
  }
423
- class CapabilityImplementation {
424
- name;
425
- instructions;
426
- schema;
427
- validateFor;
428
- apply;
429
- summarize;
430
- requestJsonSchema;
431
- constructor(name, instructions, schema, validateFor, apply, summarize) {
432
- this.name = name;
433
- this.instructions = instructions;
434
- this.schema = schema;
435
- this.validateFor = validateFor;
436
- this.apply = apply;
437
- this.summarize = summarize;
438
- this.requestJsonSchema = inputJsonSchema(schema, `capability '${name}' schema`);
439
- }
440
- [compileCapabilityBrand]({ id, stateSchema, callbacks, }) {
441
- const validate = callbacks.registerSync((_, input) => issues(this.schema, input));
442
- const validateFor = this.validateFor
443
- ? callbacks.registerSync((state, input) => validationProblems(this.validateFor(parseJson(stateSchema, state, `${id} state`), parseJson(this.schema, input, `${id} capability '${this.name}' request`)), `${id} capability '${this.name}' contextual validation`))
444
- : undefined;
445
- const apply = callbacks.registerSync((state, input) => serializeBoundary(stateSchema, this.apply(parseJson(stateSchema, state, `${id} state`), parseJson(this.schema, input, `${id} capability '${this.name}' request`)), `${id} applied state`));
446
- const summary = callbacks.registerSync((_, input) => this.summarize(parseJson(this.schema, input, `${id} capability '${this.name}' request`)));
373
+ export function capability(definition) {
374
+ const { name, instructions, schema, validateFor, apply, summarize } = definition;
375
+ requireInstructions(instructions, `Capability '${name}' instructions`);
376
+ const requestJsonSchema = inputJsonSchema(schema, `capability '${name}' schema`);
377
+ const compile = ({ id, stateSchema, callbacks }) => {
378
+ const parseState = (state) => parseJson(stateSchema, state, `${id} state`);
379
+ const parseRequest = (input) => parseJson(schema, input, `${id} capability '${name}' request`);
447
380
  return {
448
- name: this.name,
449
- instructions: this.instructions,
450
- jsonSchema: this.requestJsonSchema,
451
- validateCallback: validate,
452
- validateForCallback: validateFor,
453
- applyCallback: apply,
454
- summaryCallback: summary,
455
- valueType: `${id}.capability.${this.name}`,
381
+ name,
382
+ instructions,
383
+ jsonSchema: requestJsonSchema,
384
+ validateCallback: callbacks.registerSync((_, input) => issues(schema, input)),
385
+ validateForCallback: validateFor
386
+ ? callbacks.registerSync((state, input) => validationProblems(validateFor(parseState(state), parseRequest(input)), `${id} capability '${name}' contextual validation`))
387
+ : undefined,
388
+ applyCallback: callbacks.registerSync((state, input) => serializeBoundary(stateSchema, apply(parseState(state), parseRequest(input)), `${id} applied state`)),
389
+ summaryCallback: callbacks.registerSync((_, input) => summarize(parseRequest(input))),
390
+ valueType: `${id}.capability.${name}`,
456
391
  };
457
- }
458
- }
459
- export function capability(definition) {
460
- requireInstructions(definition.instructions, `Capability '${definition.name}' instructions`);
461
- return new CapabilityImplementation(definition.name, definition.instructions, definition.schema, definition.validateFor, definition.apply, definition.summarize);
462
- }
463
- class AgentToolGroupImplementation {
464
- predicate;
465
- tools;
466
- [toolGroupBrand];
467
- constructor(predicate, tools) {
468
- this.predicate = predicate;
469
- this.tools = tools;
470
- this[toolGroupBrand] = predicate ?? (() => true);
471
- }
392
+ };
393
+ const record = {
394
+ name,
395
+ requestJsonSchema,
396
+ [compileCapabilityBrand]: compile,
397
+ };
398
+ return Object.freeze(record);
472
399
  }
400
+ const agentToolInvocationSchema = z.strictObject({
401
+ name: z.string(),
402
+ effect: z.enum([
403
+ "read",
404
+ "workspaceMutation",
405
+ "processExecution",
406
+ "lifecycleTransition",
407
+ "unclassified",
408
+ ]),
409
+ arguments: z.unknown(),
410
+ });
473
411
  function createToolGroup(predicate, tools) {
474
412
  if (tools.length === 0) {
475
413
  throw new TandemError("An agent tool group cannot be empty.");
@@ -484,264 +422,154 @@ function createToolGroup(predicate, tools) {
484
422
  }
485
423
  seen.add(tool);
486
424
  }
487
- return new AgentToolGroupImplementation(predicate, tools);
425
+ const record = {
426
+ [toolGroupBrand]: predicate ?? (() => true),
427
+ predicate,
428
+ tools,
429
+ };
430
+ return Object.freeze(record);
488
431
  }
489
432
  export const agentTools = {
490
433
  always: (...tools) => createToolGroup(undefined, tools),
491
434
  when: (predicate, ...tools) => createToolGroup(predicate, tools),
492
435
  };
493
- class AgentWorkspaceImplementation {
494
- path;
495
- commands;
496
- commandSource;
497
- constructor(path, commandSource) {
498
- this.path = path;
499
- this.commandSource =
500
- typeof commandSource === "function" || commandSource === undefined
501
- ? commandSource
502
- : commandSource.map(copyAgentCommand);
503
- this.commands = { [commandSelectionBrand]: this };
504
- }
505
- withTools(groups, options) {
506
- if (groups.length === 0) {
507
- throw new TandemError("An agent workspace requires tool groups.");
508
- }
509
- const implementations = groups.map((group) => {
510
- if (!(group instanceof AgentToolGroupImplementation)) {
511
- throw new TandemError("Agent tool groups must be created by agentTools.");
512
- }
513
- return group;
514
- });
515
- if (options?.interceptTool !== undefined && typeof options.interceptTool !== "function") {
516
- throw new TandemError("Workspace tool interceptor must be a function.");
517
- }
518
- return new AgentWorkspaceConfigurationImplementation(this, implementations, options?.interceptTool);
519
- }
520
- }
521
- class AgentWorkspaceConfigurationImplementation {
522
- workspace;
523
- groups;
524
- interceptTool;
525
- [workspaceBrand];
526
- constructor(workspace, groups, interceptTool) {
527
- this.workspace = workspace;
528
- this.groups = groups;
529
- this.interceptTool = interceptTool;
530
- }
531
- }
532
436
  export function agentWorkspace(definition) {
533
437
  if (typeof definition.path !== "function") {
534
438
  throw new TandemError("Workspace path is required.");
535
439
  }
536
- if (definition.commands !== undefined && typeof definition.commands !== "function") {
537
- validateAgentCommands(definition.commands, "Workspace commands");
538
- }
539
- return new AgentWorkspaceImplementation(definition.path, definition.commands);
540
- }
541
- function copyAgentCommand(command) {
542
- return command.arguments === undefined
543
- ? { ...command }
544
- : {
545
- ...command,
546
- arguments: [...command.arguments],
547
- };
548
- }
549
- function validateAgentCommands(commands, context) {
550
- if (!Array.isArray(commands)) {
551
- throw new TandemError(`${context} must be an array.`);
552
- }
553
- for (const [commandIndex, command] of commands.entries()) {
554
- const commandContext = `${context}[${commandIndex}]`;
555
- if (typeof command !== "object" || command === null) {
556
- throw new TandemError(`${commandContext} must be a command.`);
557
- }
558
- const candidate = command;
559
- if (typeof candidate.name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate.name)) {
560
- throw new TandemError(`${commandContext}.name must be a valid tool name.`);
561
- }
562
- if (typeof candidate.description !== "string" || candidate.description.trim().length === 0) {
563
- throw new TandemError(`${commandContext}.description must be non-blank.`);
564
- }
565
- if (typeof candidate.command !== "string" || candidate.command.trim().length === 0) {
566
- throw new TandemError(`${commandContext}.command must be non-blank.`);
567
- }
568
- if (candidate.arguments !== undefined && !Array.isArray(candidate.arguments)) {
569
- throw new TandemError(`${commandContext}.arguments must be an array of strings.`);
570
- }
571
- const argumentList = candidate.arguments ?? [];
572
- if (argumentList.length > 16) {
573
- throw new TandemError(`${commandContext}.arguments accepts at most 16 arguments.`);
574
- }
575
- for (const [argumentIndex, argument] of argumentList.entries()) {
576
- const argumentContext = `${commandContext}.arguments[${argumentIndex}]`;
577
- if (typeof argument !== "string") {
578
- throw new TandemError(`${argumentContext} must be a string.`);
579
- }
580
- if (argument.trim().length === 0) {
581
- throw new TandemError(`${argumentContext} must not be blank.`);
440
+ const workspace = Object.freeze({
441
+ path: definition.path,
442
+ // Parsing copies a static catalogue, so later mutation by the caller cannot change it.
443
+ commandSource: typeof definition.commands === "function" || definition.commands === undefined
444
+ ? definition.commands
445
+ : parseDefinition(agentCommandsSchema, definition.commands, "Workspace commands"),
446
+ });
447
+ return Object.freeze({
448
+ commands: Object.freeze({ [commandSelectionBrand]: workspace }),
449
+ withTools(groups, options) {
450
+ if (groups.length === 0) {
451
+ throw new TandemError("An agent workspace requires tool groups.");
582
452
  }
583
- if (argument.length > 200) {
584
- throw new TandemError(`${argumentContext} must be at most 200 characters.`);
453
+ const records = groups.map((group) => {
454
+ if (!("tools" in group)) {
455
+ throw new TandemError("Agent tool groups must be created by agentTools.");
456
+ }
457
+ // A tool group record is the object `agentTools` returned.
458
+ return group;
459
+ });
460
+ if (options?.interceptTool !== undefined && typeof options.interceptTool !== "function") {
461
+ throw new TandemError("Workspace tool interceptor must be a function.");
585
462
  }
586
- }
587
- }
463
+ const configuration = {
464
+ [workspaceBrand]: keepState,
465
+ workspace,
466
+ groups: records,
467
+ interceptTool: options?.interceptTool,
468
+ };
469
+ return Object.freeze(configuration);
470
+ },
471
+ });
588
472
  }
473
+ const agentCommandsSchema = z.array(z.strictObject({
474
+ name: z
475
+ .string({ error: "must be a valid tool name" })
476
+ .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, { error: "must be a valid tool name" }),
477
+ description: nonBlankString,
478
+ command: nonBlankString,
479
+ arguments: z
480
+ .array(z
481
+ .string({ error: "must be a string" })
482
+ .refine((value) => value.trim().length > 0, { error: "must not be blank" })
483
+ .max(200, { error: "must be at most 200 characters" }), { error: "must be an array of strings" })
484
+ .max(16, { error: "accepts at most 16 arguments" })
485
+ .optional(),
486
+ }), { error: "must be an array" });
487
+ const skillSchema = z.object({ directory: nonBlankString });
589
488
  export function skill(definition) {
590
- if (typeof definition.directory !== "string" || definition.directory.trim().length === 0) {
591
- throw new TandemError("Skill directory must be a non-blank string.");
592
- }
593
- return { directory: definition.directory };
594
- }
595
- class AgentImplementation extends NodeImplementation {
596
- instructions;
597
- client;
598
- message;
599
- output;
600
- granted;
601
- skills;
602
- workspace;
603
- temperature;
604
- maxOutputTokens;
605
- reasoning;
606
- continueSession;
607
- checkpoint;
608
- timeoutMs;
609
- kind = "agent";
610
- constructor(id, persist, instructions, client, message, output, granted, skills, workspace, temperature, maxOutputTokens, reasoning, continueSession, checkpoint, timeoutMs) {
611
- super(id, persist);
612
- this.instructions = instructions;
613
- this.client = client;
614
- this.message = message;
615
- this.output = output;
616
- this.granted = granted;
617
- this.skills = skills;
618
- this.workspace = workspace;
619
- this.temperature = temperature;
620
- this.maxOutputTokens = maxOutputTokens;
621
- this.reasoning = reasoning;
622
- this.continueSession = continueSession;
623
- this.checkpoint = checkpoint;
624
- this.timeoutMs = timeoutMs;
625
- }
489
+ return parseDefinition(skillSchema, definition, "Skill");
626
490
  }
491
+ const capabilityReference = z.custom();
492
+ const agentOutputSchema = z.discriminatedUnion("raw", [
493
+ z.object({
494
+ raw: z.literal(true),
495
+ schema: z.undefined({ error: "is forbidden for raw output" }).optional(),
496
+ parse: z.custom((value) => typeof value === "function", {
497
+ error: "must be a function for raw output",
498
+ }),
499
+ }),
500
+ z.object({
501
+ raw: z.undefined().optional(),
502
+ parse: z.undefined({ error: "requires raw output mode" }).optional(),
503
+ }),
504
+ ], { error: "must be true" });
505
+ const agentOptionsSchema = z
506
+ .object({
507
+ output: agentOutputSchema.optional(),
508
+ reasoning: z
509
+ .object({
510
+ effort: z
511
+ .enum(["none", "low", "medium", "high"], {
512
+ error: "must be 'none', 'low', 'medium' or 'high'",
513
+ })
514
+ .optional(),
515
+ maxTokens: z
516
+ .int32({ error: "must be a 32-bit integer of at least 1024" })
517
+ .min(1024)
518
+ .optional(),
519
+ })
520
+ .refine((value) => (value.effort === undefined) !== (value.maxTokens === undefined), {
521
+ error: "must specify exactly one of effort or maxTokens",
522
+ })
523
+ .optional(),
524
+ capabilities: z
525
+ .array(capabilityReference)
526
+ .refine((items) => hasUnique(items, (item) => item.name), {
527
+ error: "must not contain a duplicate capability",
528
+ })
529
+ .optional(),
530
+ skills: z
531
+ .array(skillSchema)
532
+ .refine((items) => hasUnique(items, (item) => item.directory), {
533
+ error: "must not repeat a skill directory",
534
+ })
535
+ .optional(),
536
+ temperature: z.number({ error: "must be between 0 and 2" }).min(0).max(2).optional(),
537
+ maxOutputTokens: positiveInt32.optional(),
538
+ checkpoint: z
539
+ .object({
540
+ contextWindowTokens: positiveInt32,
541
+ maxOutputTokens: positiveInt32,
542
+ checkpointAtPercent: z.int({ error: "must be between 1 and 99" }).min(1).max(99),
543
+ capability: capabilityReference,
544
+ session: z.enum(["retain", "reset"], { error: "must be 'retain' or 'reset'" }).optional(),
545
+ disableCompaction: z.boolean({ error: "must be a boolean" }).optional(),
546
+ })
547
+ .refine((value) => value.maxOutputTokens < value.contextWindowTokens, {
548
+ path: ["maxOutputTokens"],
549
+ error: "must be smaller than contextWindowTokens",
550
+ })
551
+ .optional(),
552
+ })
553
+ .refine((value) => value.checkpoint === undefined ||
554
+ (value.capabilities ?? []).includes(value.checkpoint.capability), { path: ["checkpoint", "capability"], error: "must be attached to the agent" });
627
555
  export function agent(definition) {
628
556
  const rawOutput = definition.output && "raw" in definition.output && definition.output.raw === true;
629
557
  if (!rawOutput || typeof definition.instructions !== "string") {
630
558
  requireInstructions(definition.instructions, `Agent '${definition.id}' instructions`);
631
559
  }
632
- const reasoningEffort = definition.reasoning?.effort;
633
- const reasoningMaxTokens = definition.reasoning?.maxTokens;
634
- if (definition.reasoning &&
635
- (reasoningEffort !== undefined) === (reasoningMaxTokens !== undefined)) {
636
- throw new TandemError(`Agent '${definition.id}' reasoning must specify exactly one of effort or maxTokens.`);
637
- }
638
- if (reasoningEffort !== undefined &&
639
- !["none", "low", "medium", "high"].includes(reasoningEffort)) {
640
- throw new TandemError(`Agent '${definition.id}' has an invalid reasoning effort.`);
641
- }
642
- if (reasoningMaxTokens !== undefined) {
643
- if (!Number.isSafeInteger(reasoningMaxTokens) ||
644
- reasoningMaxTokens < 1024 ||
645
- reasoningMaxTokens > 2_147_483_647) {
646
- throw new TandemError(`Agent '${definition.id}' reasoning maxTokens must be a 32-bit integer of at least 1024.`);
647
- }
648
- }
649
- if (definition.output) {
650
- if (!rawOutput || typeof definition.output.instructions !== "string") {
651
- requireInstructions(definition.output.instructions, `Agent '${definition.id}' output instructions`);
652
- }
653
- if ("raw" in definition.output) {
654
- if (definition.output.raw !== true) {
655
- throw new TandemError(`Agent '${definition.id}' output raw must be true.`);
656
- }
657
- if ("schema" in definition.output) {
658
- throw new TandemError(`Agent '${definition.id}' output schema is forbidden for raw output.`);
659
- }
660
- if (typeof definition.output.parse !== "function") {
661
- throw new TandemError(`Agent '${definition.id}' raw output requires a parse function.`);
662
- }
663
- }
664
- else if ("parse" in definition.output) {
665
- throw new TandemError(`Agent '${definition.id}' output parse requires raw output mode.`);
666
- }
667
- }
668
- const capabilities = definition.capabilities ?? [];
669
- const names = new Set();
670
- for (const item of capabilities) {
671
- if (names.has(item.name)) {
672
- throw new TandemError(`Agent '${definition.id}' has duplicate capability '${item.name}'.`);
673
- }
674
- else {
675
- names.add(item.name);
676
- }
677
- }
678
- const skills = definition.skills ?? [];
679
- const skillDirectories = new Set();
680
- for (const item of skills) {
681
- if (typeof item?.directory !== "string" || item.directory.trim().length === 0) {
682
- throw new TandemError(`Agent '${definition.id}' has a skill with an invalid directory.`);
683
- }
684
- if (!skillDirectories.add(item.directory)) {
685
- throw new TandemError(`Agent '${definition.id}' has the skill directory '${item.directory}' more than once.`);
686
- }
687
- }
688
- if (definition.temperature !== undefined &&
689
- (!Number.isFinite(definition.temperature) ||
690
- definition.temperature < 0 ||
691
- definition.temperature > 2)) {
692
- throw new TandemError(`Agent '${definition.id}' temperature must be between 0 and 2.`);
693
- }
694
- if (definition.maxOutputTokens !== undefined &&
695
- (!Number.isSafeInteger(definition.maxOutputTokens) ||
696
- definition.maxOutputTokens <= 0 ||
697
- definition.maxOutputTokens > 2_147_483_647)) {
698
- throw new TandemError(`Agent '${definition.id}' maxOutputTokens must be a positive 32-bit integer.`);
560
+ if (definition.output && (!rawOutput || typeof definition.output.instructions !== "string")) {
561
+ requireInstructions(definition.output.instructions, `Agent '${definition.id}' output instructions`);
699
562
  }
700
563
  if (definition.checkpoint) {
701
- const checkpoint = definition.checkpoint;
702
- if (!Number.isSafeInteger(checkpoint.contextWindowTokens) ||
703
- checkpoint.contextWindowTokens <= 0 ||
704
- checkpoint.contextWindowTokens > 2_147_483_647) {
705
- throw new TandemError(`Agent '${definition.id}' checkpoint contextWindowTokens must be a positive 32-bit integer.`);
706
- }
707
- if (!Number.isSafeInteger(checkpoint.maxOutputTokens) ||
708
- checkpoint.maxOutputTokens <= 0 ||
709
- checkpoint.maxOutputTokens > 2_147_483_647 ||
710
- checkpoint.maxOutputTokens >= checkpoint.contextWindowTokens) {
711
- throw new TandemError(`Agent '${definition.id}' checkpoint maxOutputTokens must be a positive 32-bit integer smaller than contextWindowTokens.`);
712
- }
713
- if (!Number.isSafeInteger(checkpoint.checkpointAtPercent) ||
714
- checkpoint.checkpointAtPercent <= 0 ||
715
- checkpoint.checkpointAtPercent >= 100) {
716
- throw new TandemError(`Agent '${definition.id}' checkpoint checkpointAtPercent must be between 1 and 99.`);
717
- }
718
- if (!capabilities.includes(checkpoint.capability)) {
719
- throw new TandemError(`Agent '${definition.id}' checkpoint capability must be attached to the agent.`);
720
- }
721
- requireInstructions(checkpoint.instructions, `Agent '${definition.id}' checkpoint instructions`);
722
- if (checkpoint.session !== undefined &&
723
- checkpoint.session !== "retain" &&
724
- checkpoint.session !== "reset") {
725
- throw new TandemError(`Agent '${definition.id}' checkpoint session must be 'retain' or 'reset'.`);
726
- }
727
- if (checkpoint.disableCompaction !== undefined &&
728
- typeof checkpoint.disableCompaction !== "boolean") {
729
- throw new TandemError(`Agent '${definition.id}' checkpoint disableCompaction must be a boolean.`);
730
- }
731
- }
732
- return new AgentImplementation(definition.id, definition.persist, definition.instructions, definition.client, definition.message, definition.output, capabilities, skills, definition.workspace, definition.temperature, definition.maxOutputTokens, definition.reasoning, definition.continueSession ?? false, definition.checkpoint, definition.timeoutMs);
733
- }
734
- class ParallelImplementation extends NodeImplementation {
735
- branches;
736
- max;
737
- merge;
738
- kind = "parallel";
739
- constructor(id, persist, branches, max, merge) {
740
- super(id, persist);
741
- this.branches = branches;
742
- this.max = max;
743
- this.merge = merge;
564
+ requireInstructions(definition.checkpoint.instructions, `Agent '${definition.id}' checkpoint instructions`);
744
565
  }
566
+ parseDefinition(agentOptionsSchema, definition, `Agent '${definition.id}'`);
567
+ const record = {
568
+ ...definition,
569
+ [participantBrand]: keepState,
570
+ kind: "agent",
571
+ };
572
+ return Object.freeze(record);
745
573
  }
746
574
  export function parallel(definition) {
747
575
  if (!definition) {
@@ -749,33 +577,43 @@ export function parallel(definition) {
749
577
  }
750
578
  return createParallel(definition);
751
579
  }
580
+ const parallelOptionsSchema = z.object({
581
+ max: positiveInt32.optional(),
582
+ branches: z
583
+ .record(z.string(), z.custom())
584
+ .refine((branches) => Object.keys(branches).every((id) => id.trim().length > 0), {
585
+ error: "must not have a blank branch ID",
586
+ })
587
+ .refine((branches) => Object.keys(branches).length >= 2, {
588
+ error: "requires at least two branches",
589
+ })
590
+ .refine((branches) => hasUnique(Object.values(branches), (participant) => participant), {
591
+ error: "must own a distinct participant per branch",
592
+ }),
593
+ });
752
594
  function createParallel(definition) {
753
- if (definition.max !== undefined &&
754
- (!Number.isInteger(definition.max) || definition.max < 1 || definition.max > 2147483647)) {
755
- throw new TandemError(`Parallel group '${definition.id}' max must be a positive 32-bit integer.`);
756
- }
757
- const entries = Object.entries(definition.branches);
758
- if (entries.length < 2) {
759
- throw new TandemError(`Parallel group '${definition.id}' requires at least two branches.`);
760
- }
761
- const participants = new Set(entries.map(([, participant]) => participant));
762
- if (participants.size !== entries.length) {
763
- throw new TandemError(`Parallel group '${definition.id}' must own a distinct participant per branch.`);
764
- }
765
- return new ParallelImplementation(definition.id, definition.persist, definition.branches, definition.max, definition.merge);
766
- }
767
- class TerminalImplementation extends NodeImplementation {
768
- failed;
769
- summary;
770
- kind = "terminal";
771
- constructor(id, persist, failed, summary) {
772
- super(id, persist);
773
- this.failed = failed;
774
- this.summary = summary;
775
- }
595
+ parseDefinition(parallelOptionsSchema, definition, `Parallel group '${definition.id}'`);
596
+ const record = {
597
+ ...definition,
598
+ [participantBrand]: keepState,
599
+ kind: "parallel",
600
+ };
601
+ return Object.freeze(record);
776
602
  }
777
603
  export function output(definition) {
778
- return new TerminalImplementation(definition.id, definition.persist, definition.failed ?? false, definition.summary);
604
+ const record = {
605
+ ...definition,
606
+ [participantBrand]: keepState,
607
+ kind: "terminal",
608
+ };
609
+ return Object.freeze(record);
610
+ }
611
+ /**
612
+ * Every node is the frozen record its factory built; the exported node interfaces are opaque views
613
+ * of those records, with the request/output/item types erased here.
614
+ */
615
+ function nodeRecord(node) {
616
+ return node;
779
617
  }
780
618
  export function route(definition) {
781
619
  return definition;
@@ -784,43 +622,39 @@ export function pipeline(definition) {
784
622
  const start = definition.start;
785
623
  const members = new Set(definition.nodes);
786
624
  if (members.size !== definition.nodes.length) {
787
- throw new Error("Pipeline nodes must contain each participant object exactly once.");
625
+ throw new TandemError("Pipeline nodes must contain each participant object exactly once.");
788
626
  }
789
627
  const ids = new Set(definition.nodes.map((node) => node.id));
790
628
  if (ids.size !== definition.nodes.length) {
791
- throw new Error("Pipeline node IDs must be unique.");
629
+ throw new TandemError("Pipeline node IDs must be unique.");
792
630
  }
793
631
  const ownedParticipants = new Set();
794
- for (const node of definition.nodes) {
632
+ for (const node of definition.nodes.map(nodeRecord)) {
795
633
  if (node.kind !== "parallel") {
796
634
  continue;
797
635
  }
798
- const parallelNode = node;
799
- for (const [branchId, participant] of Object.entries(parallelNode.branches)) {
800
- if (branchId.trim().length === 0) {
801
- throw new Error(`Parallel group '${node.id}' contains a blank branch ID.`);
802
- }
636
+ for (const participant of Object.values(node.branches)) {
803
637
  if (members.has(participant)) {
804
- throw new Error(`Parallel branch participant '${participant.id}' cannot also be a parent pipeline node.`);
638
+ throw new TandemError(`Parallel branch participant '${participant.id}' cannot also be a parent pipeline node.`);
805
639
  }
806
640
  if (!ownedParticipants.add(participant)) {
807
- throw new Error(`Parallel branch participant '${participant.id}' is owned more than once.`);
641
+ throw new TandemError(`Parallel branch participant '${participant.id}' is owned more than once.`);
808
642
  }
809
643
  if (ids.has(participant.id)) {
810
- throw new Error(`Pipeline participant ID '${participant.id}' must be globally unique.`);
644
+ throw new TandemError(`Pipeline participant ID '${participant.id}' must be globally unique.`);
811
645
  }
812
646
  ids.add(participant.id);
813
647
  }
814
648
  }
815
649
  if (!members.has(definition.start)) {
816
- throw new Error(`Pipeline start '${definition.start.id}' must be the registered participant object.`);
650
+ throw new TandemError(`Pipeline start '${definition.start.id}' must be the registered participant object.`);
817
651
  }
818
652
  if (start.kind === "terminal") {
819
- throw new Error(`Pipeline start '${start.id}' cannot be a terminal.`);
653
+ throw new TandemError(`Pipeline start '${start.id}' cannot be a terminal.`);
820
654
  }
821
655
  for (const item of definition.routes) {
822
656
  if (!members.has(item.from) || !members.has(item.to)) {
823
- throw new Error(`Route '${item.label}' endpoints must be registered participant objects.`);
657
+ throw new TandemError(`Route '${item.label}' endpoints must be registered participant objects.`);
824
658
  }
825
659
  }
826
660
  const unconditionalRoutes = new Map();
@@ -831,22 +665,21 @@ export function pipeline(definition) {
831
665
  const key = `${item.from.id}\u0000${item.outcome ?? "default"}`;
832
666
  const existing = unconditionalRoutes.get(key);
833
667
  if (existing) {
834
- throw new Error(`Routes '${existing.label}' and '${item.label}' are both unconditional from '${item.from.id}'.`);
668
+ throw new TandemError(`Routes '${existing.label}' and '${item.label}' are both unconditional from '${item.from.id}'.`);
835
669
  }
836
670
  unconditionalRoutes.set(key, item);
837
671
  }
838
672
  if (new Set(definition.outputs).size !== definition.outputs.length) {
839
- throw new Error("Pipeline outputs must contain each terminal exactly once.");
673
+ throw new TandemError("Pipeline outputs must contain each terminal exactly once.");
840
674
  }
841
675
  for (const item of definition.outputs) {
842
676
  if (!members.has(item)) {
843
- throw new Error(`Output '${item.id}' must be the registered participant object.`);
677
+ throw new TandemError(`Output '${item.id}' must be the registered participant object.`);
844
678
  }
845
679
  }
846
680
  const reachable = new Set([start]);
847
681
  const pending = [start];
848
- while (pending.length > 0) {
849
- const source = pending.pop();
682
+ for (let source = pending.pop(); source !== undefined; source = pending.pop()) {
850
683
  for (const item of definition.routes) {
851
684
  if (item.from === source && !reachable.has(item.to)) {
852
685
  reachable.add(item.to);
@@ -857,12 +690,12 @@ export function pipeline(definition) {
857
690
  const outputs = new Set(definition.outputs);
858
691
  for (const node of reachable) {
859
692
  if (node.kind === "terminal" && !outputs.has(node)) {
860
- throw new Error(`Reachable terminal '${node.id}' must be listed in outputs.`);
693
+ throw new TandemError(`Reachable terminal '${node.id}' must be listed in outputs.`);
861
694
  }
862
695
  }
863
696
  for (const item of definition.outputs) {
864
697
  if (!reachable.has(item)) {
865
- throw new Error(`Output '${item.id}' must be reachable from start '${start.id}'.`);
698
+ throw new TandemError(`Output '${item.id}' must be reachable from start '${start.id}'.`);
866
699
  }
867
700
  }
868
701
  return { ...definition, persist: definition.persist ?? false };
@@ -870,65 +703,61 @@ export function pipeline(definition) {
870
703
  /** Projects an instantiated pipeline without compiling, registering, or invoking callbacks. */
871
704
  export function inspectPipeline(graph) {
872
705
  const inspectNode = (node) => {
873
- const implementation = node;
874
- const persist = implementation.persist;
875
- if (implementation instanceof CollectionImplementation) {
876
- return {
877
- id: node.id,
878
- kind: "collection",
879
- persist,
880
- max: implementation.definition.max,
881
- agents: implementation.definition.agents.map((agent) => node.id + "/" + agent.id),
882
- };
883
- }
884
- if (node.kind === "interaction") {
885
- const interaction = implementation;
886
- return {
887
- id: node.id,
888
- kind: "interaction",
889
- persist,
890
- interaction: {
891
- requestSchema: z.toJSONSchema(interaction.requestSchema, { io: "input" }),
892
- responseSchema: z.toJSONSchema(interaction.responseSchema, { io: "input" }),
893
- },
894
- };
895
- }
896
- if (node.kind === "agent") {
897
- const agent = implementation;
898
- return {
899
- id: node.id,
900
- kind: "agent",
901
- persist,
902
- agent: {
903
- capabilities: agent.granted.map((item) => ({
904
- name: item.name,
905
- requestSchema: JSON.parse(item.requestJsonSchema),
906
- })),
907
- ...(!agent.output || "raw" in agent.output
908
- ? {}
909
- : { outputSchema: z.toJSONSchema(agent.output.schema, { io: "input" }) }),
910
- workspace: agent.workspace !== undefined,
911
- },
912
- };
913
- }
914
- if (node.kind === "parallel") {
915
- const parallel = implementation;
916
- return {
917
- id: node.id,
918
- kind: "parallel",
919
- persist,
920
- ...(parallel.max !== undefined ? { max: parallel.max } : {}),
921
- branches: Object.entries(parallel.branches).map(([id, participant]) => ({
706
+ const record = nodeRecord(node);
707
+ const { id, persist } = record;
708
+ switch (record.kind) {
709
+ case "stage":
710
+ return { id, kind: "stage", persist };
711
+ case "terminal":
712
+ return { id, kind: record.failed ? "failure" : "completion", persist };
713
+ case "collection":
714
+ return {
922
715
  id,
923
- participant: inspectNode(participant),
924
- })),
925
- };
926
- }
927
- if (node.kind === "terminal") {
928
- const terminal = implementation;
929
- return { id: node.id, kind: terminal.failed ? "failure" : "completion", persist };
716
+ kind: "collection",
717
+ persist,
718
+ max: record.max,
719
+ agents: record.agents.map((agent) => `${id}/${agent.id}`),
720
+ };
721
+ case "interaction":
722
+ return {
723
+ id,
724
+ kind: "interaction",
725
+ persist,
726
+ interaction: {
727
+ requestSchema: z.toJSONSchema(record.requestSchema, { io: "input" }),
728
+ responseSchema: z.toJSONSchema(record.responseSchema, { io: "input" }),
729
+ },
730
+ };
731
+ case "agent":
732
+ return {
733
+ id,
734
+ kind: "agent",
735
+ persist,
736
+ agent: {
737
+ capabilities: (record.capabilities ?? []).map((item) => ({
738
+ name: item.name,
739
+ requestSchema: JSON.parse(item.requestJsonSchema),
740
+ })),
741
+ ...(!record.output || "raw" in record.output
742
+ ? {}
743
+ : { outputSchema: z.toJSONSchema(record.output.schema, { io: "input" }) }),
744
+ workspace: record.workspace !== undefined,
745
+ },
746
+ };
747
+ case "parallel":
748
+ return {
749
+ id,
750
+ kind: "parallel",
751
+ persist,
752
+ ...(record.max !== undefined ? { max: record.max } : {}),
753
+ branches: Object.entries(record.branches).map(([branchId, participant]) => ({
754
+ id: branchId,
755
+ participant: inspectNode(participant),
756
+ })),
757
+ };
758
+ default:
759
+ return record;
930
760
  }
931
- return { id: node.id, kind: "stage", persist };
932
761
  };
933
762
  return {
934
763
  name: graph.name,
@@ -948,112 +777,59 @@ export function inspectPipeline(graph) {
948
777
  outputs: graph.outputs.map((item) => item.id),
949
778
  };
950
779
  }
951
- const runObservationSchema = z.discriminatedUnion("kind", [
952
- z
953
- .object({
954
- version: z.literal(1),
955
- kind: z.literal("stepStarted"),
956
- stepId: z.string().min(1),
957
- visitId: z.string().nullable().optional(),
958
- })
959
- .strict(),
960
- z
961
- .object({
962
- version: z.literal(1),
963
- kind: z.literal("stepCompleted"),
964
- stepId: z.string().min(1),
965
- visitId: z.string().nullable().optional(),
966
- })
967
- .strict(),
968
- z
969
- .object({
970
- version: z.literal(1),
971
- kind: z.literal("stepCancelled"),
972
- stepId: z.string().min(1),
973
- visitId: z.string().nullable().optional(),
974
- })
975
- .strict(),
976
- z
977
- .object({
978
- version: z.literal(1),
979
- kind: z.literal("stepFaulted"),
980
- stepId: z.string().min(1),
981
- visitId: z.string().nullable().optional(),
982
- error: z.string(),
983
- })
984
- .strict(),
985
- z
986
- .object({
987
- version: z.literal(1),
988
- kind: z.literal("agentText"),
989
- stepId: z.string().min(1),
990
- visitId: z.string().nullable().optional(),
991
- text: z.string(),
992
- })
993
- .strict(),
994
- z
995
- .object({
996
- version: z.literal(1),
997
- kind: z.literal("agentModelSelected"),
998
- stepId: z.string().min(1),
999
- visitId: z.string().nullable().optional(),
1000
- modelId: z.string().min(1),
1001
- })
1002
- .strict(),
1003
- z
1004
- .object({
1005
- version: z.literal(1),
1006
- kind: z.literal("agentReasoning"),
1007
- stepId: z.string().min(1),
1008
- visitId: z.string().nullable().optional(),
1009
- text: z.string(),
1010
- })
1011
- .strict(),
1012
- z
1013
- .object({
1014
- version: z.literal(1),
1015
- kind: z.literal("agentUsage"),
1016
- stepId: z.string().min(1),
1017
- visitId: z.string().nullable().optional(),
1018
- inputTokens: z.number().int().nonnegative(),
1019
- outputTokens: z.number().int().nonnegative(),
1020
- reasoningTokens: z.number().int().nonnegative(),
1021
- currentContextTokens: z.number().int().nonnegative(),
1022
- contextWindowTokens: z.number().int().nonnegative().nullable(),
1023
- })
1024
- .strict(),
1025
- z
1026
- .object({
1027
- version: z.literal(1),
1028
- kind: z.literal("structuredOutputRejected"),
1029
- stepId: z.string().min(1),
1030
- visitId: z.string().nullable().optional(),
780
+ const observationBase = z.object({
781
+ version: z.literal(1),
782
+ stepId: z.string().min(1),
783
+ visitId: z.string().nullable().optional(),
784
+ });
785
+ const observation = (kind, shape) => observationBase.extend({ kind: z.literal(kind), ...shape }).strict();
786
+ const tokenCount = z.number().int().nonnegative();
787
+ const runObservationSchema = z
788
+ .discriminatedUnion("kind", [
789
+ observation("stepStarted", {}),
790
+ observation("stepCompleted", {}),
791
+ observation("stepCancelled", {}),
792
+ observation("stepFaulted", { error: z.string() }),
793
+ observation("agentText", { text: z.string() }),
794
+ observation("agentReasoning", { text: z.string() }),
795
+ observation("agentModelSelected", { modelId: z.string().min(1) }),
796
+ observation("agentUsage", {
797
+ inputTokens: tokenCount,
798
+ outputTokens: tokenCount,
799
+ reasoningTokens: tokenCount,
800
+ currentContextTokens: tokenCount,
801
+ contextWindowTokens: tokenCount.nullable(),
802
+ }),
803
+ observation("structuredOutputRejected", {
1031
804
  attempt: z.number().int().positive(),
1032
- problems: z.array(z.object({ field: z.string(), message: z.string().min(1) }).strict()),
805
+ problems: z
806
+ .array(z
807
+ .object({ field: z.string(), message: z.string().min(1) })
808
+ .strict()
809
+ .readonly())
810
+ .readonly(),
1033
811
  rawResponse: z.string(),
1034
- })
1035
- .strict(),
1036
- ]);
1037
- const acceptedKinds = [
1038
- "StructuredOutputAccepted",
1039
- "CapabilityAccepted",
1040
- "InteractionRequested",
1041
- "InteractionAnswered",
1042
- "StepCompleted",
1043
- ];
1044
- const acceptedValueSchema = z
812
+ }),
813
+ ])
814
+ .readonly();
815
+ const acceptedValueFields = z
1045
816
  .object({
1046
- kind: z.enum(acceptedKinds),
1047
- visitId: z.string().nullable().optional(),
817
+ kind: z.enum([
818
+ "StructuredOutputAccepted",
819
+ "CapabilityAccepted",
820
+ "InteractionRequested",
821
+ "InteractionAnswered",
822
+ "StepCompleted",
823
+ ]),
1048
824
  stepId: z.string().min(1),
825
+ visitId: z.string().nullable().optional(),
1049
826
  valueType: z.string().min(1).nullable(),
1050
- payload: z.unknown().nullable(),
827
+ payload: z.unknown(),
1051
828
  })
1052
- .strict()
1053
- .refine((value) => value.valueType !== null || value.payload !== null, {
829
+ .strict();
830
+ const acceptedValuesSchema = z.array(acceptedValueFields.refine((value) => value.valueType !== null || value.payload !== null, {
1054
831
  message: "valueType and payload cannot both be null",
1055
- });
1056
- const acceptedValuesSchema = z.array(acceptedValueSchema);
832
+ }));
1057
833
  const runResultSchema = z
1058
834
  .object({
1059
835
  runId: z.uuid(),
@@ -1062,9 +838,11 @@ const runResultSchema = z
1062
838
  summary: z.string().nullable(),
1063
839
  })
1064
840
  .strict();
841
+ /** Lazy (and cached by the module loader) so importing the authoring API never starts .NET. */
842
+ const loadRuntime = () => import("./runtime/loader.mjs");
1065
843
  export async function inspectAccepted(options) {
1066
844
  try {
1067
- const { inspectAcceptedAsync } = await import("./runtime/loader.mjs");
845
+ const { inspectAcceptedAsync } = await loadRuntime();
1068
846
  return parseJson(acceptedValuesSchema, await inspectAcceptedAsync(options.ledgerPath, options.runId), "accepted values").map((value) => ({
1069
847
  version: 1,
1070
848
  kind: value.kind,
@@ -1089,33 +867,31 @@ export async function run(graph, initial, options = {}) {
1089
867
  const callbacks = new CallbackRegistry();
1090
868
  try {
1091
869
  const nodes = graph.nodes.map((node) => compileNode(node, graph.state, callbacks));
1092
- const routes = graph.routes.map((item) => {
1093
- const callback = item.when
1094
- ? callbacks.registerSync((state) => String(item.when(parseJson(graph.state, state, `route '${item.label}' state`))))
1095
- : undefined;
1096
- return {
1097
- source: item.from.id,
1098
- target: item.to.id,
1099
- label: item.label,
1100
- outcome: item.outcome,
1101
- predicateCallback: callback,
1102
- };
1103
- });
870
+ const routes = graph.routes.map(({ from, to, label, outcome, when }) => ({
871
+ source: from.id,
872
+ target: to.id,
873
+ label,
874
+ outcome,
875
+ predicateCallback: when
876
+ ? callbacks.registerSync((state) => String(when(parseJson(graph.state, state, `route '${label}' state`))))
877
+ : undefined,
878
+ }));
1104
879
  const handlerEntries = interactionHandlerEntries(options.interactions);
1105
880
  const members = new Set(graph.nodes);
1106
881
  const interactionHandlers = handlerEntries.map((entry, index) => {
1107
882
  if (!members.has(entry.interaction)) {
1108
883
  throw new TandemError(`Interaction handler '${entry.interaction.id}' must target a participant in pipeline '${graph.name}'.`);
1109
884
  }
1110
- const implementation = entry.interaction;
885
+ const { id, requestSchema, responseSchema } = entry.interaction;
1111
886
  const handleCallback = callbacks.registerAsync(async (_, input, signal) => {
1112
- const response = await entry.handle(parseJson(implementation.requestSchema, input, `${implementation.id} request input`), { signal });
887
+ const response = await entry.handle(parseJson(requestSchema, input, `${id} request input`), { signal });
1113
888
  signal.throwIfAborted();
1114
- return serializeBoundary(implementation.responseSchema, response, `${implementation.id} response`);
889
+ return serializeBoundary(responseSchema, response, `${id} response`);
1115
890
  });
1116
891
  return { id: `h${index}`, target: entry.interaction.id, handleCallback };
1117
892
  });
1118
- const observationCallback = options.observe
893
+ const { observe } = options;
894
+ const observationCallback = observe
1119
895
  ? callbacks.registerAsync(async (_, input, signal) => {
1120
896
  const event = parseJson(runObservationSchema, input, "run observation");
1121
897
  const observationSignal = options.signal?.aborted === true
@@ -1123,11 +899,11 @@ export async function run(graph, initial, options = {}) {
1123
899
  : event.kind === "stepCancelled" && !signal.aborted
1124
900
  ? AbortSignal.abort()
1125
901
  : signal;
1126
- await options.observe(event, { signal: observationSignal });
902
+ await observe(event, { signal: observationSignal });
1127
903
  return "";
1128
904
  })
1129
905
  : undefined;
1130
- const { runRegisteredGraphAsync } = await import("./runtime/loader.mjs");
906
+ const { runRegisteredGraphAsync } = await loadRuntime();
1131
907
  const resultJson = await runRegisteredGraphAsync(JSON.stringify({
1132
908
  contractVersion: 10,
1133
909
  name: graph.name,
@@ -1143,7 +919,12 @@ export async function run(graph, initial, options = {}) {
1143
919
  routes,
1144
920
  outputs: graph.outputs.map((item) => item.id),
1145
921
  interactionHandlers,
1146
- }), (id, state, input) => callbacks.invokeSync(id, state, input), (id, state, input, signal) => callbacks.invokeAsync(id, state, input, signal), options.signal);
922
+ }), (id, state, input) => {
923
+ const result = callbacks.invoke(id, state, input, neverAborted);
924
+ return typeof result === "string"
925
+ ? result
926
+ : callbackFailed(new Error(`Internal callback '${id}' is asynchronous.`));
927
+ }, async (id, state, input, signal) => callbacks.invoke(id, state, input, signal), options.signal);
1147
928
  const result = parseJson(runResultSchema, resultJson, "run result");
1148
929
  return { ...result, state: parse(graph.state, result.state, "final state") };
1149
930
  }
@@ -1155,7 +936,7 @@ export async function run(graph, initial, options = {}) {
1155
936
  if (callbackFailure) {
1156
937
  throw new ContractValidationError(callbackFailure.boundary, callbackFailure.problems);
1157
938
  }
1158
- if (isCancellationError(error, options.signal?.aborted === true)) {
939
+ if (isCancellation(error, options.signal)) {
1159
940
  throw new TandemCancellationError(error);
1160
941
  }
1161
942
  throw new TandemRuntimeError("run", error);
@@ -1165,27 +946,13 @@ export async function run(graph, initial, options = {}) {
1165
946
  }
1166
947
  }
1167
948
  function participantPersists(node) {
1168
- const implementation = node;
1169
- if (implementation.persist) {
1170
- return true;
1171
- }
1172
- return implementation instanceof ParallelImplementation
1173
- ? Object.values(implementation.branches).some((branch) => branch.persist === true)
1174
- : false;
949
+ const record = nodeRecord(node);
950
+ return (record.persist === true ||
951
+ (record.kind === "parallel" && Object.values(record.branches).some(participantPersists)));
1175
952
  }
1176
953
  function issues(schema, input) {
1177
- let value;
1178
- try {
1179
- value = JSON.parse(input);
1180
- }
1181
- catch {
1182
- return JSON.stringify([{ path: "$", message: "Invalid JSON" }]);
1183
- }
1184
- return issuesParsed(schema, value);
1185
- }
1186
- function issuesParsed(schema, value) {
1187
954
  try {
1188
- parse(schema, value, "agent contract");
955
+ parseJson(schema, input, "agent contract");
1189
956
  return "";
1190
957
  }
1191
958
  catch (error) {
@@ -1206,180 +973,182 @@ function requireInstructions(instructions, boundary) {
1206
973
  ]);
1207
974
  }
1208
975
  }
976
+ /** Authoring errors read as "<subject> <field path> <problem>", e.g. "Agent 'a' checkpoint session must be ...". */
977
+ function parseDefinition(schema, value, subject) {
978
+ const result = schema.safeParse(value);
979
+ if (result.success) {
980
+ return result.data;
981
+ }
982
+ const problems = result.error.issues.map((issue) => {
983
+ const field = issue.path
984
+ .map((part) => (typeof part === "number" ? `[${part}]` : ` ${String(part)}`))
985
+ .join("");
986
+ return `${field} ${issue.message}`;
987
+ });
988
+ throw new TandemError(`${subject}${problems.join(";")}.`);
989
+ }
1209
990
  function interactionHandlerEntries(handlers) {
1210
991
  if (!handlers) {
1211
992
  return [];
1212
993
  }
1213
- if (!(handlers instanceof InteractionHandlersImplementation)) {
994
+ const entries = registeredHandlers.get(handlers);
995
+ if (!entries) {
1214
996
  throw new TandemError("interactions must be created by interactions().");
1215
997
  }
1216
- return handlers.entries;
998
+ return entries;
1217
999
  }
1218
1000
  function compileNode(node, stateSchema, callbacks) {
1219
- const implementation = node;
1220
- const base = { id: node.id, persist: implementation.persist };
1221
- if (implementation instanceof CollectionImplementation) {
1222
- const definition = implementation.definition;
1223
- const agents = definition.agents.map((value) => {
1224
- if (!(value instanceof TaskAgentImplementation))
1225
- throw new TandemError("Use taskAgent to declare collection agents.");
1226
- return compileNode(value.participant, value.state, callbacks);
1227
- });
1228
- const itemsCallback = callbacks.registerSync((state) => serializeBoundary(z.array(definition.item), definition.items(parseJson(stateSchema, state, node.id + " state")), node.id + " items"));
1229
- const runCallback = callbacks.registerAsync(async (item, scopeId, signal) => {
1230
- let active = false;
1231
- let closed = false;
1232
- const context = {
1233
- signal,
1234
- async run(value, input) {
1235
- if (closed)
1236
- throw new TandemError("Collection scope has ended.");
1237
- if (active)
1238
- throw new TandemError("Collection item agents must be awaited serially.");
1239
- if (!definition.agents.includes(value) || !(value instanceof TaskAgentImplementation)) {
1240
- throw new TandemError("Agent is not declared in this collection.");
1241
- }
1242
- signal.throwIfAborted();
1243
- active = true;
1244
- try {
1245
- const { runCollectionAgentAsync } = await import("./runtime/loader.mjs");
1246
- const response = await runCollectionAgentAsync(scopeId, value.id, serializeBoundary(value.state, { input, output: null }, value.id + " input"));
1247
- const state = parseJson(value.state, response, value.id + " output");
1248
- if (state.output === null)
1249
- throw new TandemError("Agent returned no output.");
1250
- return parse(value.result, state.output.value, value.id + " result");
1251
- }
1252
- finally {
1253
- active = false;
1254
- }
1255
- },
1001
+ const record = nodeRecord(node);
1002
+ const { id } = record;
1003
+ const base = { id, persist: record.persist };
1004
+ const parseState = (state, boundary) => parseJson(stateSchema, state, `${id} ${boundary}`);
1005
+ switch (record.kind) {
1006
+ case "stage":
1007
+ return {
1008
+ ...base,
1009
+ kind: "stage",
1010
+ runCallback: callbacks.registerAsync(async (state, _, signal) => serializeBoundary(stateSchema, await record.execute(parseState(state, "input"), { signal }), `${id} output`)),
1256
1011
  };
1257
- try {
1258
- const result = await definition.execute(parseJson(definition.item, item, node.id + " item"), context);
1259
- if (active)
1260
- throw new TandemError("Collection item returned before its agent completed.");
1261
- signal.throwIfAborted();
1262
- return serializeBoundary(definition.result, result, node.id + " result");
1263
- }
1264
- finally {
1265
- closed = true;
1266
- }
1267
- });
1268
- const applyCallback = callbacks.registerSync((state, results) => serializeBoundary(stateSchema, definition.apply(parseJson(stateSchema, state, node.id + " state"), parseJson(z.array(definition.result), results, node.id + " results")), node.id + " output"));
1269
- return {
1270
- ...base,
1271
- kind: "collection",
1272
- agents,
1273
- max: definition.max,
1274
- itemsCallback,
1275
- runCallback,
1276
- applyCallback,
1277
- };
1012
+ case "terminal":
1013
+ return {
1014
+ ...base,
1015
+ kind: record.failed ? "failure" : "completion",
1016
+ summaryCallback: callbacks.registerSync((state) => record.summary(parseState(state, "state"))),
1017
+ };
1018
+ case "interaction":
1019
+ return {
1020
+ ...base,
1021
+ kind: "interaction",
1022
+ requestCallback: callbacks.registerSync((state) => serializeBoundary(record.requestSchema, record.request(parseState(state, "state")), `${id} request`)),
1023
+ applyCallback: callbacks.registerSync((state, input) => serializeBoundary(stateSchema, record.apply(parseState(state, "state"), parseJson(record.responseSchema, input, `${id} response input`)), `${id} applied state`)),
1024
+ };
1025
+ case "collection":
1026
+ return compileCollection(record, stateSchema, callbacks);
1027
+ case "agent":
1028
+ return compileAgent(record, stateSchema, callbacks);
1029
+ case "parallel": {
1030
+ const branches = Object.entries(record.branches);
1031
+ const branchStates = z.strictObject(Object.fromEntries(branches.map(([branchId]) => [branchId, stateSchema])));
1032
+ return {
1033
+ ...base,
1034
+ kind: "parallel",
1035
+ ...(record.max !== undefined ? { max: record.max } : {}),
1036
+ branches: branches.map(([branchId, participant]) => ({
1037
+ id: branchId,
1038
+ participant: compileNode(participant, stateSchema, callbacks),
1039
+ })),
1040
+ mergeCallback: callbacks.registerSync((state, input) => serializeBoundary(stateSchema, record.merge(parseState(state, "merge baseline"), parseJson(branchStates, input, `${id} merge branches`)), `${id} merged state`)),
1041
+ };
1042
+ }
1043
+ default:
1044
+ return record;
1278
1045
  }
1279
- if (implementation instanceof StageImplementation) {
1280
- const run = callbacks.registerAsync(async (state, _, signal) => serializeBoundary(stateSchema, await implementation.execute(parseJson(stateSchema, state, `${node.id} input`), {
1046
+ }
1047
+ function compileCollection(collection, stateSchema, callbacks) {
1048
+ const { id } = collection;
1049
+ const agents = collection.agents.map((reference) => {
1050
+ const task = taskAgentRecord(reference);
1051
+ return compileNode(task.participant, task.state, callbacks);
1052
+ });
1053
+ const itemsCallback = callbacks.registerSync((state) => serializeBoundary(z.array(collection.item), collection.items(parseJson(stateSchema, state, `${id} state`)), `${id} items`));
1054
+ const runCallback = callbacks.registerAsync(async (item, scopeId, signal) => {
1055
+ let active = false;
1056
+ let closed = false;
1057
+ const context = {
1281
1058
  signal,
1282
- }), `${node.id} output`));
1283
- return { ...base, kind: "stage", runCallback: run };
1284
- }
1285
- if (implementation instanceof InteractionImplementation) {
1286
- const request = callbacks.registerSync((state) => serializeBoundary(implementation.requestSchema, implementation.request(parseJson(stateSchema, state, `${node.id} state`)), `${node.id} request`));
1287
- const apply = callbacks.registerSync((state, input) => serializeBoundary(stateSchema, implementation.apply(parseJson(stateSchema, state, `${node.id} state`), parseJson(implementation.responseSchema, input, `${node.id} response input`)), `${node.id} applied state`));
1288
- return {
1289
- ...base,
1290
- kind: "interaction",
1291
- requestCallback: request,
1292
- applyCallback: apply,
1293
- };
1294
- }
1295
- if (implementation instanceof AgentImplementation) {
1296
- const message = callbacks.registerSync((state) => implementation.message(parseJson(stateSchema, state, `${node.id} message state`)));
1297
- const output = implementation.output
1298
- ? compileAgentOutput(node.id, implementation.output, stateSchema, callbacks)
1299
- : undefined;
1300
- const capabilities = implementation.granted.map((item) => item[compileCapabilityBrand]({ id: node.id, stateSchema, callbacks }));
1301
- const workspace = implementation.workspace
1302
- ? compileWorkspace(node.id, implementation.workspace, stateSchema, callbacks)
1303
- : undefined;
1304
- return {
1305
- ...base,
1306
- kind: "agent",
1307
- instructions: implementation.instructions,
1308
- client: { ...implementation.client, verifyModel: implementation.client.verifyModel ?? false },
1309
- messageCallback: message,
1310
- output,
1311
- capabilities,
1312
- skillDirectories: implementation.skills.map((item) => item.directory),
1313
- temperature: implementation.temperature,
1314
- maxOutputTokens: implementation.maxOutputTokens,
1315
- reasoning: implementation.reasoning,
1316
- continueSession: implementation.continueSession,
1317
- checkpoint: implementation.checkpoint
1318
- ? {
1319
- contextWindowTokens: implementation.checkpoint.contextWindowTokens,
1320
- maxOutputTokens: implementation.checkpoint.maxOutputTokens,
1321
- checkpointAtPercent: implementation.checkpoint.checkpointAtPercent,
1322
- capabilityName: implementation.checkpoint.capability.name,
1323
- instructions: implementation.checkpoint.instructions,
1324
- messageCallback: callbacks.registerSync((state, input) => implementation.checkpoint.message(parseJson(stateSchema, state, `${node.id} checkpoint state`), Number(input))),
1325
- resetSession: (implementation.checkpoint.session ?? "reset") === "reset",
1326
- disableCompaction: implementation.checkpoint.disableCompaction ?? false,
1059
+ async run(value, input) {
1060
+ if (closed)
1061
+ throw new TandemError("Collection scope has ended.");
1062
+ if (active)
1063
+ throw new TandemError("Collection item agents must be awaited serially.");
1064
+ if (!collection.agents.includes(value)) {
1065
+ throw new TandemError("Agent is not declared in this collection.");
1327
1066
  }
1328
- : undefined,
1329
- timeoutMilliseconds: implementation.timeoutMs,
1330
- workspace,
1067
+ const task = taskAgentRecord(value);
1068
+ signal.throwIfAborted();
1069
+ active = true;
1070
+ try {
1071
+ const { runCollectionAgentAsync } = await loadRuntime();
1072
+ const response = await runCollectionAgentAsync(scopeId, task.id, serializeBoundary(task.state, { input, output: null }, `${task.id} input`));
1073
+ const state = parseJson(task.state, response, `${task.id} output`);
1074
+ if (state.output === null)
1075
+ throw new TandemError("Agent returned no output.");
1076
+ return parse(task.result, state.output.value, `${task.id} result`);
1077
+ }
1078
+ finally {
1079
+ active = false;
1080
+ }
1081
+ },
1331
1082
  };
1332
- }
1333
- if (implementation instanceof ParallelImplementation) {
1334
- const entries = Object.entries(implementation.branches);
1335
- const branchIds = entries.map(([branchId]) => branchId);
1336
- const mergeCallback = callbacks.registerSync((state, input) => {
1337
- const baseline = parseJson(stateSchema, state, `${node.id} merge baseline`);
1338
- let raw;
1339
- try {
1340
- raw = JSON.parse(input);
1341
- }
1342
- catch {
1343
- throw new ContractValidationError(`${node.id} merge branches`, [
1344
- { path: "$", message: "Invalid JSON" },
1345
- ]);
1346
- }
1347
- if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
1348
- throw new ContractValidationError(`${node.id} merge branches`, [
1349
- { path: "$", message: "Expected a branch-state object." },
1350
- ]);
1351
- }
1352
- const values = raw;
1353
- if (!isDeepStrictEqual(Object.keys(values).sort(), [...branchIds].sort())) {
1354
- throw new ContractValidationError(`${node.id} merge branches`, [
1355
- { path: "$", message: "Branch-state keys do not match the authored branches." },
1356
- ]);
1083
+ try {
1084
+ const result = await collection.execute(parseJson(collection.item, item, `${id} item`), context);
1085
+ if (active)
1086
+ throw new TandemError("Collection item returned before its agent completed.");
1087
+ signal.throwIfAborted();
1088
+ return serializeBoundary(collection.result, result, `${id} result`);
1089
+ }
1090
+ finally {
1091
+ closed = true;
1092
+ }
1093
+ });
1094
+ const applyCallback = callbacks.registerSync((state, results) => serializeBoundary(stateSchema, collection.apply(parseJson(stateSchema, state, `${id} state`), parseJson(z.array(collection.result), results, `${id} results`)), `${id} output`));
1095
+ return {
1096
+ id,
1097
+ persist: collection.persist,
1098
+ kind: "collection",
1099
+ agents,
1100
+ max: collection.max,
1101
+ itemsCallback,
1102
+ runCallback,
1103
+ applyCallback,
1104
+ };
1105
+ }
1106
+ function compileAgent(agent, stateSchema, callbacks) {
1107
+ const { id, checkpoint } = agent;
1108
+ const message = callbacks.registerSync((state) => agent.message(parseJson(stateSchema, state, `${id} message state`)));
1109
+ const output = agent.output
1110
+ ? compileAgentOutput(id, agent.output, stateSchema, callbacks)
1111
+ : undefined;
1112
+ const capabilities = (agent.capabilities ?? []).map((item) => item[compileCapabilityBrand]({ id, stateSchema, callbacks }));
1113
+ const workspace = agent.workspace
1114
+ ? compileWorkspace(id, agent.workspace, stateSchema, callbacks)
1115
+ : undefined;
1116
+ return {
1117
+ id,
1118
+ persist: agent.persist,
1119
+ kind: "agent",
1120
+ instructions: agent.instructions,
1121
+ client: { ...agent.client, verifyModel: agent.client.verifyModel ?? false },
1122
+ messageCallback: message,
1123
+ output,
1124
+ capabilities,
1125
+ skillDirectories: (agent.skills ?? []).map((item) => item.directory),
1126
+ temperature: agent.temperature,
1127
+ maxOutputTokens: agent.maxOutputTokens,
1128
+ reasoning: agent.reasoning,
1129
+ continueSession: agent.continueSession ?? false,
1130
+ checkpoint: checkpoint
1131
+ ? {
1132
+ contextWindowTokens: checkpoint.contextWindowTokens,
1133
+ maxOutputTokens: checkpoint.maxOutputTokens,
1134
+ checkpointAtPercent: checkpoint.checkpointAtPercent,
1135
+ capabilityName: checkpoint.capability.name,
1136
+ instructions: checkpoint.instructions,
1137
+ messageCallback: callbacks.registerSync((state, input) => checkpoint.message(parseJson(stateSchema, state, `${id} checkpoint state`), Number(input))),
1138
+ resetSession: (checkpoint.session ?? "reset") === "reset",
1139
+ disableCompaction: checkpoint.disableCompaction ?? false,
1357
1140
  }
1358
- const parsed = Object.fromEntries(branchIds.map((branchId) => [
1359
- branchId,
1360
- parse(stateSchema, values[branchId], `${node.id} branch '${branchId}' state`),
1361
- ]));
1362
- return serializeBoundary(stateSchema, implementation.merge(baseline, parsed), `${node.id} merged state`);
1363
- });
1364
- return {
1365
- ...base,
1366
- kind: "parallel",
1367
- ...(implementation.max !== undefined ? { max: implementation.max } : {}),
1368
- branches: entries.map(([id, participant]) => ({
1369
- id,
1370
- participant: compileNode(participant, stateSchema, callbacks),
1371
- })),
1372
- mergeCallback,
1373
- };
1374
- }
1375
- const terminal = implementation;
1376
- const summary = callbacks.registerSync((state) => terminal.summary(parseJson(stateSchema, state, `${node.id} state`)));
1377
- return { ...base, kind: terminal.failed ? "failure" : "completion", summaryCallback: summary };
1141
+ : undefined,
1142
+ timeoutMilliseconds: agent.timeoutMs,
1143
+ workspace,
1144
+ };
1378
1145
  }
1379
- function compileWorkspace(id, configuration, stateSchema, callbacks) {
1380
- if (!(configuration instanceof AgentWorkspaceConfigurationImplementation)) {
1146
+ function compileWorkspace(id, authored, stateSchema, callbacks) {
1147
+ if (!("groups" in authored)) {
1381
1148
  throw new TandemError(`Agent '${id}' workspace must be created by agentWorkspace().withTools().`);
1382
1149
  }
1150
+ // A workspace configuration is the record `withTools()` returned.
1151
+ const configuration = authored;
1383
1152
  const workspace = configuration.workspace;
1384
1153
  const pathCallback = callbacks.registerSync((state) => {
1385
1154
  const value = workspace.path(parseJson(stateSchema, state, `${id} workspace path state`));
@@ -1390,18 +1159,10 @@ function compileWorkspace(id, configuration, stateSchema, callbacks) {
1390
1159
  });
1391
1160
  const commandsCallback = callbacks.registerSync((state) => {
1392
1161
  const typedState = parseJson(stateSchema, state, `${id} workspace commands state`);
1393
- const value = typeof workspace.commandSource === "function"
1394
- ? workspace.commandSource(typedState)
1162
+ const commands = typeof workspace.commandSource === "function"
1163
+ ? parseDefinition(agentCommandsSchema, workspace.commandSource(typedState), `Agent '${id}' workspace commands`)
1395
1164
  : (workspace.commandSource ?? []);
1396
- validateAgentCommands(value, `Agent '${id}' workspace commands`);
1397
- return serializeBoundary(z.array(z
1398
- .object({
1399
- name: z.string(),
1400
- description: z.string(),
1401
- command: z.string(),
1402
- arguments: z.array(z.string()).optional(),
1403
- })
1404
- .strict()), value, `${id} workspace commands`);
1165
+ return JSON.stringify(commands);
1405
1166
  });
1406
1167
  const selected = new Set();
1407
1168
  const toolGroups = configuration.groups.map((group, index) => {
@@ -1429,9 +1190,10 @@ function compileWorkspace(id, configuration, stateSchema, callbacks) {
1429
1190
  includeCommands = true;
1430
1191
  }
1431
1192
  }
1432
- const whenCallback = group.predicate
1193
+ const { predicate } = group;
1194
+ const whenCallback = predicate
1433
1195
  ? callbacks.registerSync((state) => {
1434
- const value = group.predicate(parseJson(stateSchema, state, `${id} tool group ${index} state`));
1196
+ const value = predicate(parseJson(stateSchema, state, `${id} tool group ${index} state`));
1435
1197
  if (typeof value !== "boolean") {
1436
1198
  throw new TandemError(`Agent '${id}' tool group ${index} predicate must return a boolean.`);
1437
1199
  }
@@ -1440,17 +1202,10 @@ function compileWorkspace(id, configuration, stateSchema, callbacks) {
1440
1202
  : undefined;
1441
1203
  return { tools, includeCommands, whenCallback };
1442
1204
  });
1443
- const interceptCallback = configuration.interceptTool
1205
+ const { interceptTool } = configuration;
1206
+ const interceptCallback = interceptTool
1444
1207
  ? callbacks.registerAsync(async (state, input, signal) => {
1445
- const typedState = parseJson(stateSchema, state, `${id} tool interception state`);
1446
- let invocation;
1447
- try {
1448
- invocation = JSON.parse(input);
1449
- }
1450
- catch {
1451
- throw new TandemError(`Agent '${id}' received an invalid tool interception payload.`);
1452
- }
1453
- const result = await configuration.interceptTool(typedState, invocation, { signal });
1208
+ const result = await interceptTool(parseJson(stateSchema, state, `${id} tool interception state`), parseJson(agentToolInvocationSchema, input, `${id} tool interception payload`), { signal });
1454
1209
  if (result !== null && typeof result !== "string") {
1455
1210
  throw new TandemError(`Agent '${id}' tool interceptor must return a string or null.`);
1456
1211
  }
@@ -1460,27 +1215,34 @@ function compileWorkspace(id, configuration, stateSchema, callbacks) {
1460
1215
  return { pathCallback, commandsCallback, toolGroups, interceptCallback };
1461
1216
  }
1462
1217
  function compileAgentOutput(id, output, stateSchema, callbacks) {
1463
- const raw = output.raw === true;
1464
- const validate = raw
1465
- ? callbacks.registerSync((_, input) => issuesParsed(z.unknown(), output.parse(JSON.parse(input))))
1466
- : callbacks.registerSync((_, input) => issues(output.schema, input));
1467
- const validateFor = output.validateFor
1468
- ? callbacks.registerSync((state, input) => validationProblems(output.validateFor(parseJson(stateSchema, state, `${id} state`), raw ? JSON.parse(input) : parseJson(output.schema, input, `${id} output`)), `${id} output contextual validation`))
1469
- : undefined;
1470
- const apply = callbacks.registerSync((state, input) => serializeBoundary(stateSchema, output.apply(parseJson(stateSchema, state, `${id} state`), raw ? JSON.parse(input) : parseJson(output.schema, input, `${id} output`)), `${id} applied state`));
1218
+ let decode;
1219
+ let format;
1220
+ if ("raw" in output) {
1221
+ const { parse } = output;
1222
+ // The bridge hands back exactly the JSON that rawParseCallback produced from `parse`.
1223
+ decode = (input) => JSON.parse(input);
1224
+ format = {
1225
+ raw: true,
1226
+ rawParseCallback: callbacks.registerSync((_, input) => JSON.stringify(parse(input))),
1227
+ };
1228
+ }
1229
+ else {
1230
+ const { schema } = output;
1231
+ decode = (input) => parseJson(schema, input, `${id} output`);
1232
+ format = {
1233
+ jsonSchema: inputJsonSchema(schema, `${id} output schema`),
1234
+ validateCallback: callbacks.registerSync((_, input) => issues(schema, input)),
1235
+ };
1236
+ }
1237
+ const { validateFor, apply } = output;
1238
+ const parseState = (state) => parseJson(stateSchema, state, `${id} state`);
1471
1239
  return {
1472
1240
  instructions: output.instructions,
1473
- ...(raw
1474
- ? {
1475
- raw: true,
1476
- rawParseCallback: callbacks.registerSync((_, input) => JSON.stringify(output.parse(input))),
1477
- }
1478
- : {
1479
- jsonSchema: inputJsonSchema(output.schema, `${id} output schema`),
1480
- validateCallback: validate,
1481
- }),
1482
- validateForCallback: validateFor,
1483
- applyCallback: apply,
1241
+ ...format,
1242
+ validateForCallback: validateFor
1243
+ ? callbacks.registerSync((state, input) => validationProblems(validateFor(parseState(state), decode(input)), `${id} output contextual validation`))
1244
+ : undefined,
1245
+ applyCallback: callbacks.registerSync((state, input) => serializeBoundary(stateSchema, apply(parseState(state), decode(input)), `${id} applied state`)),
1484
1246
  valueType: `${id}.output`,
1485
1247
  };
1486
1248
  }