@maxanstey-meridian/tandem 0.1.2 → 0.1.3
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.
- package/README.md +16 -2
- package/dist/runtime/Humanizer.dll +0 -0
- package/dist/runtime/Json.More.dll +0 -0
- package/dist/runtime/JsonPointer.Net.dll +0 -0
- package/dist/runtime/JsonSchema.Net.Generation.dll +0 -0
- package/dist/runtime/JsonSchema.Net.dll +0 -0
- package/dist/runtime/Tandem.Advanced.dll +0 -0
- package/dist/runtime/Tandem.Ledger.dll +0 -0
- package/dist/runtime/Tandem.NodeApiSpike.Bridge.deps.json +170 -110
- package/dist/runtime/Tandem.NodeApiSpike.Bridge.dll +0 -0
- package/dist/runtime/Tandem.OpenAICompatible.dll +0 -0
- package/dist/runtime/Tandem.Terminal.dll +0 -0
- package/dist/runtime/Tandem.dll +0 -0
- package/dist/runtime/Tavily.dll +0 -0
- package/dist/runtime/cs/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/cs/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/de/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/de/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/es/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/es/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/fr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/fr/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/import.cjs +3 -0
- package/dist/runtime/it/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/it/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/ja/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/ja/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/ko/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/ko/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/pl/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/pl/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/pt-BR/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/ru/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/ru/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/tr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/tr/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/zh-Hans/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/dist/runtime/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
- package/dist/runtime/zh-Hant/Microsoft.CodeAnalysis.resources.dll +0 -0
- package/package.json +6 -12
- package/runtime/FluentValidation.dll +0 -0
- package/runtime/Google.Protobuf.dll +0 -0
- package/runtime/Microsoft.Agents.AI.Abstractions.dll +0 -0
- package/runtime/Microsoft.Agents.AI.Harness.dll +0 -0
- package/runtime/Microsoft.Agents.AI.Tools.Shell.dll +0 -0
- package/runtime/Microsoft.Agents.AI.Workflows.dll +0 -0
- package/runtime/Microsoft.Agents.AI.dll +0 -0
- package/runtime/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- package/runtime/Microsoft.CodeAnalysis.dll +0 -0
- package/runtime/Microsoft.Data.Sqlite.dll +0 -0
- package/runtime/Microsoft.Extensions.AI.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.AI.Evaluation.dll +0 -0
- package/runtime/Microsoft.Extensions.AI.OpenAI.dll +0 -0
- package/runtime/Microsoft.Extensions.AI.dll +0 -0
- package/runtime/Microsoft.Extensions.Caching.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.Compliance.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.Configuration.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.DependencyInjection.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.Diagnostics.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.FileProviders.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.FileSystemGlobbing.dll +0 -0
- package/runtime/Microsoft.Extensions.Hosting.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.Logging.Abstractions.dll +0 -0
- package/runtime/Microsoft.Extensions.ObjectPool.dll +0 -0
- package/runtime/Microsoft.Extensions.Options.dll +0 -0
- package/runtime/Microsoft.Extensions.Primitives.dll +0 -0
- package/runtime/Microsoft.Extensions.VectorData.Abstractions.dll +0 -0
- package/runtime/Microsoft.JavaScript.NodeApi.dll +0 -0
- package/runtime/Microsoft.ML.Tokenizers.dll +0 -0
- package/runtime/OpenAI.dll +0 -0
- package/runtime/OpenTelemetry.Api.dll +0 -0
- package/runtime/SQLitePCLRaw.batteries_v2.dll +0 -0
- package/runtime/SQLitePCLRaw.core.dll +0 -0
- package/runtime/SQLitePCLRaw.provider.e_sqlite3.dll +0 -0
- package/runtime/Spectre.Console.Ansi.dll +0 -0
- package/runtime/Spectre.Console.dll +0 -0
- package/runtime/System.ClientModel.dll +0 -0
- package/runtime/System.Memory.Data.dll +0 -0
- package/runtime/System.Numerics.Tensors.dll +0 -0
- package/runtime/System.Reflection.MetadataLoadContext.dll +0 -0
- package/runtime/Tandem.Advanced.dll +0 -0
- package/runtime/Tandem.Ledger.dll +0 -0
- package/runtime/Tandem.NodeApiSpike.Bridge.cjs +0 -26
- package/runtime/Tandem.NodeApiSpike.Bridge.d.ts +0 -23
- package/runtime/Tandem.NodeApiSpike.Bridge.deps.json +0 -1097
- package/runtime/Tandem.NodeApiSpike.Bridge.dll +0 -0
- package/runtime/Tandem.NodeApiSpike.Bridge.mjs +0 -31
- package/runtime/Tandem.NodeApiSpike.Bridge.runtimeconfig.json +0 -13
- package/runtime/Tandem.OpenAICompatible.dll +0 -0
- package/runtime/Tandem.Terminal.dll +0 -0
- package/runtime/Tandem.dll +0 -0
- package/runtime/libe_sqlite3.dylib +0 -0
- package/runtime/loader.d.mts +0 -10
- package/runtime/loader.mjs +0 -35
- package/scripts/install-runtime.mjs +0 -75
- package/src/cli.ts +0 -72
- package/src/index.ts +0 -2096
- package/src/runtime/loader.d.mts +0 -10
- package/tsconfig.json +0 -14
package/src/index.ts
DELETED
|
@@ -1,2096 +0,0 @@
|
|
|
1
|
-
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
|
|
4
|
-
type SyncCallback = (state: string, input: string) => string;
|
|
5
|
-
type AsyncCallback = (state: string, input: string, signal: AbortSignal) => Promise<string>;
|
|
6
|
-
const participantBrand: unique symbol = Symbol("participant");
|
|
7
|
-
const compileCapabilityBrand: unique symbol = Symbol("compileCapability");
|
|
8
|
-
const interactionHandlersBrand: unique symbol = Symbol("interactionHandlers");
|
|
9
|
-
const workspaceBrand: unique symbol = Symbol("workspace");
|
|
10
|
-
const toolGroupBrand: unique symbol = Symbol("toolGroup");
|
|
11
|
-
const commandSelectionBrand: unique symbol = Symbol("commandSelection");
|
|
12
|
-
|
|
13
|
-
export class TandemError extends Error {
|
|
14
|
-
constructor(message: string, options?: ErrorOptions) {
|
|
15
|
-
super(message, options);
|
|
16
|
-
this.name = "TandemError";
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
export class TandemRuntimeError extends TandemError {
|
|
20
|
-
constructor(
|
|
21
|
-
readonly operation: "run" | "inspect",
|
|
22
|
-
cause: unknown,
|
|
23
|
-
) {
|
|
24
|
-
super(`Tandem ${operation} failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
|
|
25
|
-
cause,
|
|
26
|
-
});
|
|
27
|
-
this.name = "TandemRuntimeError";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export class TandemCancellationError extends TandemRuntimeError {
|
|
31
|
-
constructor(cause: unknown) {
|
|
32
|
-
super("run", cause);
|
|
33
|
-
this.name = "AbortError";
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export class ContractValidationError extends TandemError {
|
|
37
|
-
readonly problems: readonly ValidationProblem[];
|
|
38
|
-
constructor(
|
|
39
|
-
readonly boundary: string,
|
|
40
|
-
problems: readonly ValidationProblem[],
|
|
41
|
-
) {
|
|
42
|
-
super(
|
|
43
|
-
`${boundary} validation failed: ${problems.map((p) => `${p.path}: ${p.message}`).join("; ")}`,
|
|
44
|
-
);
|
|
45
|
-
this.name = "ContractValidationError";
|
|
46
|
-
this.problems = problems;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type ValidationProblem = {
|
|
51
|
-
readonly path: string;
|
|
52
|
-
readonly message: string;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
type CallbackFailure = {
|
|
56
|
-
readonly boundary: string;
|
|
57
|
-
readonly problems: readonly ValidationProblem[];
|
|
58
|
-
};
|
|
59
|
-
type CallbackResult =
|
|
60
|
-
| { readonly succeeded: true; readonly value: string }
|
|
61
|
-
| {
|
|
62
|
-
readonly succeeded: false;
|
|
63
|
-
readonly error: {
|
|
64
|
-
readonly name: string;
|
|
65
|
-
readonly message: string;
|
|
66
|
-
readonly boundary?: string;
|
|
67
|
-
readonly problems?: readonly ValidationProblem[];
|
|
68
|
-
};
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function callbackError(error: unknown): Extract<CallbackResult, { succeeded: false }>["error"] {
|
|
72
|
-
return error instanceof ContractValidationError
|
|
73
|
-
? {
|
|
74
|
-
name: error.name,
|
|
75
|
-
message: error.message,
|
|
76
|
-
boundary: error.boundary,
|
|
77
|
-
problems: error.problems,
|
|
78
|
-
}
|
|
79
|
-
: {
|
|
80
|
-
name: error instanceof Error ? error.name : "Error",
|
|
81
|
-
message: error instanceof Error ? error.message : String(error),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
class CallbackRegistry {
|
|
86
|
-
readonly #sync = new Map<string, SyncCallback>();
|
|
87
|
-
readonly #async = new Map<string, AsyncCallback>();
|
|
88
|
-
#next = 0;
|
|
89
|
-
#disposed = false;
|
|
90
|
-
|
|
91
|
-
registerSync(callback: SyncCallback): string {
|
|
92
|
-
const id = this.#allocate();
|
|
93
|
-
this.#sync.set(id, callback);
|
|
94
|
-
return id;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
registerAsync(callback: AsyncCallback): string {
|
|
98
|
-
const id = this.#allocate();
|
|
99
|
-
this.#async.set(id, callback);
|
|
100
|
-
return id;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
invokeSync(id: string, state: string, input: string): string {
|
|
104
|
-
try {
|
|
105
|
-
const callback = this.#sync.get(id);
|
|
106
|
-
if (!callback) {
|
|
107
|
-
throw new Error(`Unknown internal callback '${id}'.`);
|
|
108
|
-
}
|
|
109
|
-
return JSON.stringify({
|
|
110
|
-
succeeded: true,
|
|
111
|
-
value: callback(state, input),
|
|
112
|
-
} satisfies CallbackResult);
|
|
113
|
-
} catch (error) {
|
|
114
|
-
return JSON.stringify({
|
|
115
|
-
succeeded: false,
|
|
116
|
-
error: callbackError(error),
|
|
117
|
-
} satisfies CallbackResult);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async invokeAsync(
|
|
122
|
-
id: string,
|
|
123
|
-
state: string,
|
|
124
|
-
input: string,
|
|
125
|
-
signal: AbortSignal,
|
|
126
|
-
): Promise<string> {
|
|
127
|
-
try {
|
|
128
|
-
const callback = this.#async.get(id);
|
|
129
|
-
if (!callback) {
|
|
130
|
-
throw new Error(`Unknown internal async callback '${id}'.`);
|
|
131
|
-
}
|
|
132
|
-
return JSON.stringify({
|
|
133
|
-
succeeded: true,
|
|
134
|
-
value: await callback(state, input, signal),
|
|
135
|
-
} satisfies CallbackResult);
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return JSON.stringify({
|
|
138
|
-
succeeded: false,
|
|
139
|
-
error: callbackError(error),
|
|
140
|
-
} satisfies CallbackResult);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
dispose(): void {
|
|
145
|
-
this.#disposed = true;
|
|
146
|
-
this.#sync.clear();
|
|
147
|
-
this.#async.clear();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
#allocate(): string {
|
|
151
|
-
if (this.#disposed) {
|
|
152
|
-
throw new Error("Callback registry has been disposed.");
|
|
153
|
-
}
|
|
154
|
-
return `c${this.#next++}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function callbackContractFailure(error: unknown): CallbackFailure | null {
|
|
159
|
-
const marker = "TANDEM_CALLBACK_CONTRACT:";
|
|
160
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
-
const start = message.indexOf(marker);
|
|
162
|
-
if (start < 0) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
return JSON.parse(message.slice(start + marker.length)) as CallbackFailure;
|
|
167
|
-
} catch {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function isCancellationError(error: unknown, signalAborted: boolean): boolean {
|
|
173
|
-
if (!(error instanceof Error)) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
if (error.message.includes("JavaScript callback failed:") && !signalAborted) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
return (
|
|
180
|
-
error.name === "AbortError" ||
|
|
181
|
-
/\b(?:operation was cancel(?:l)?ed|operation was aborted|this operation was aborted)\b/i.test(
|
|
182
|
-
error.message,
|
|
183
|
-
)
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function path(parts: PropertyKey[]): string {
|
|
188
|
-
return parts.length === 0
|
|
189
|
-
? "$"
|
|
190
|
-
: `$${parts.map((part) => (typeof part === "number" ? `[${part}]` : `.${String(part)}`)).join("")}`;
|
|
191
|
-
}
|
|
192
|
-
function parseValidated<T>(schema: z.ZodType<T>, value: unknown, boundary: string): T {
|
|
193
|
-
let result: z.ZodSafeParseResult<T>;
|
|
194
|
-
try {
|
|
195
|
-
result = schema.safeParse(value);
|
|
196
|
-
} catch (error) {
|
|
197
|
-
if (error instanceof Error && /async/i.test(error.message)) {
|
|
198
|
-
throw new ContractValidationError(boundary, [
|
|
199
|
-
{
|
|
200
|
-
path: "$",
|
|
201
|
-
message:
|
|
202
|
-
"Async Zod refinements are unsupported; Tandem contracts must validate synchronously.",
|
|
203
|
-
},
|
|
204
|
-
]);
|
|
205
|
-
}
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
if (!result.success) {
|
|
209
|
-
throw new ContractValidationError(
|
|
210
|
-
boundary,
|
|
211
|
-
result.error.issues.map((issue) => ({ path: path(issue.path), message: issue.message })),
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
return result.data;
|
|
215
|
-
}
|
|
216
|
-
function parse<T>(schema: z.ZodType<T>, value: unknown, boundary: string): T {
|
|
217
|
-
const result = parseValidated(schema, value, boundary);
|
|
218
|
-
if (!isDeepStrictEqual(result, value)) {
|
|
219
|
-
throw new ContractValidationError(boundary, [
|
|
220
|
-
{
|
|
221
|
-
path: "$",
|
|
222
|
-
message:
|
|
223
|
-
"Zod contract changed the boundary value. Coercion, defaults, transforms, and stripping are unsupported.",
|
|
224
|
-
},
|
|
225
|
-
]);
|
|
226
|
-
}
|
|
227
|
-
return result;
|
|
228
|
-
}
|
|
229
|
-
function serializeBoundary<T>(schema: z.ZodType<T>, value: unknown, boundary: string): string {
|
|
230
|
-
const parsed = parse(schema, value, boundary);
|
|
231
|
-
const problem = jsonValueProblem(parsed, "$", new WeakSet<object>());
|
|
232
|
-
if (problem) {
|
|
233
|
-
throw new ContractValidationError(boundary, [problem]);
|
|
234
|
-
}
|
|
235
|
-
let json: string | undefined;
|
|
236
|
-
try {
|
|
237
|
-
json = JSON.stringify(parsed);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
throw new ContractValidationError(boundary, [
|
|
240
|
-
{
|
|
241
|
-
path: "$",
|
|
242
|
-
message: `Value is not JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,
|
|
243
|
-
},
|
|
244
|
-
]);
|
|
245
|
-
}
|
|
246
|
-
if (json === undefined) {
|
|
247
|
-
throw new ContractValidationError(boundary, [
|
|
248
|
-
{ path: "$", message: "Top-level undefined is not JSON-serializable." },
|
|
249
|
-
]);
|
|
250
|
-
}
|
|
251
|
-
const roundTripped = JSON.parse(json) as unknown;
|
|
252
|
-
if (!isDeepStrictEqual(roundTripped, parsed)) {
|
|
253
|
-
throw new ContractValidationError(boundary, [
|
|
254
|
-
{ path: "$", message: "Value is not losslessly JSON-serializable." },
|
|
255
|
-
]);
|
|
256
|
-
}
|
|
257
|
-
return json;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function jsonValueProblem(
|
|
261
|
-
value: unknown,
|
|
262
|
-
valuePath: string,
|
|
263
|
-
seen: WeakSet<object>,
|
|
264
|
-
): ValidationProblem | null {
|
|
265
|
-
if (value === undefined) {
|
|
266
|
-
return { path: valuePath, message: "undefined is not JSON-serializable." };
|
|
267
|
-
}
|
|
268
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
269
|
-
return { path: valuePath, message: "Non-finite numbers are not JSON-serializable." };
|
|
270
|
-
}
|
|
271
|
-
if (typeof value === "bigint" || typeof value === "symbol" || typeof value === "function") {
|
|
272
|
-
return { path: valuePath, message: `${typeof value} values are not JSON-serializable.` };
|
|
273
|
-
}
|
|
274
|
-
if (value === null || typeof value !== "object") {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
277
|
-
if (seen.has(value)) {
|
|
278
|
-
return { path: valuePath, message: "Cyclic values are not JSON-serializable." };
|
|
279
|
-
}
|
|
280
|
-
seen.add(value);
|
|
281
|
-
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
282
|
-
return { path: valuePath, message: "Symbol properties are not JSON-serializable." };
|
|
283
|
-
}
|
|
284
|
-
if (Array.isArray(value)) {
|
|
285
|
-
for (let index = 0; index < value.length; index++) {
|
|
286
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
287
|
-
if (!descriptor) {
|
|
288
|
-
return {
|
|
289
|
-
path: `${valuePath}[${index}]`,
|
|
290
|
-
message: "Sparse arrays are not JSON-serializable.",
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
if (!("value" in descriptor)) {
|
|
294
|
-
return {
|
|
295
|
-
path: `${valuePath}[${index}]`,
|
|
296
|
-
message: "Accessor properties are not supported at JSON boundaries.",
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
const problem = jsonValueProblem(descriptor.value, `${valuePath}[${index}]`, seen);
|
|
300
|
-
if (problem) {
|
|
301
|
-
return problem;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const additionalProperty = Object.getOwnPropertyNames(value).find((name) => {
|
|
305
|
-
if (name === "length") {
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
const index = Number(name);
|
|
309
|
-
return (
|
|
310
|
-
!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== name
|
|
311
|
-
);
|
|
312
|
-
});
|
|
313
|
-
if (additionalProperty) {
|
|
314
|
-
return {
|
|
315
|
-
path: `${valuePath}.${additionalProperty}`,
|
|
316
|
-
message: "Additional array properties are not JSON-serializable.",
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
seen.delete(value);
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
323
|
-
return {
|
|
324
|
-
path: valuePath,
|
|
325
|
-
message: "Only plain objects are JSON-serializable boundary values.",
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
for (const name of Object.getOwnPropertyNames(value)) {
|
|
329
|
-
const descriptor = Object.getOwnPropertyDescriptor(value, name);
|
|
330
|
-
if (!descriptor?.enumerable) {
|
|
331
|
-
return {
|
|
332
|
-
path: `${valuePath}.${name}`,
|
|
333
|
-
message: "Non-enumerable properties are not JSON-serializable.",
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
if (!("value" in descriptor)) {
|
|
337
|
-
return {
|
|
338
|
-
path: `${valuePath}.${name}`,
|
|
339
|
-
message: "Accessor properties are not supported at JSON boundaries.",
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
const problem = jsonValueProblem(descriptor.value, `${valuePath}.${name}`, seen);
|
|
343
|
-
if (problem) {
|
|
344
|
-
return problem;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
seen.delete(value);
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
function parseJson<T>(schema: z.ZodType<T>, json: string, boundary: string): T {
|
|
351
|
-
let value: unknown;
|
|
352
|
-
try {
|
|
353
|
-
value = JSON.parse(json);
|
|
354
|
-
} catch {
|
|
355
|
-
throw new ContractValidationError(boundary, [{ path: "$", message: "Invalid JSON" }]);
|
|
356
|
-
}
|
|
357
|
-
return parse(schema, value, boundary);
|
|
358
|
-
}
|
|
359
|
-
function inputJsonSchema<T>(schema: z.ZodType<T>, boundary: string): string {
|
|
360
|
-
try {
|
|
361
|
-
z.toJSONSchema(schema, { io: "output" });
|
|
362
|
-
return JSON.stringify(z.toJSONSchema(schema, { io: "input" }));
|
|
363
|
-
} catch (error) {
|
|
364
|
-
throw new ContractValidationError(boundary, [
|
|
365
|
-
{ path: "$", message: error instanceof Error ? error.message : String(error) },
|
|
366
|
-
]);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
interface Participant<TState> {
|
|
371
|
-
readonly id: string;
|
|
372
|
-
readonly [participantBrand]: (state: TState) => TState;
|
|
373
|
-
}
|
|
374
|
-
export interface Stage<TState> extends Participant<TState> {
|
|
375
|
-
readonly kind: "stage";
|
|
376
|
-
}
|
|
377
|
-
export interface Interaction<TState, TRequest, TResponse> extends Participant<TState> {
|
|
378
|
-
readonly kind: "interaction";
|
|
379
|
-
readonly requestType?: TRequest;
|
|
380
|
-
readonly responseType?: TResponse;
|
|
381
|
-
}
|
|
382
|
-
export interface Agent<TState> extends Participant<TState> {
|
|
383
|
-
readonly kind: "agent";
|
|
384
|
-
}
|
|
385
|
-
export interface Parallel<TState> extends Participant<TState> {
|
|
386
|
-
readonly kind: "parallel";
|
|
387
|
-
}
|
|
388
|
-
export interface Terminal<TState> extends Participant<TState> {
|
|
389
|
-
readonly kind: "terminal";
|
|
390
|
-
}
|
|
391
|
-
type Node<TState> =
|
|
392
|
-
| Stage<TState>
|
|
393
|
-
| Interaction<TState, unknown, unknown>
|
|
394
|
-
| Agent<TState>
|
|
395
|
-
| Parallel<TState>
|
|
396
|
-
| Terminal<TState>;
|
|
397
|
-
|
|
398
|
-
abstract class NodeImplementation<TState> implements Participant<TState> {
|
|
399
|
-
readonly [participantBrand]!: (state: TState) => TState;
|
|
400
|
-
constructor(
|
|
401
|
-
readonly id: string,
|
|
402
|
-
readonly persist?: boolean,
|
|
403
|
-
) {}
|
|
404
|
-
}
|
|
405
|
-
class StageImplementation<TState> extends NodeImplementation<TState> implements Stage<TState> {
|
|
406
|
-
readonly kind = "stage";
|
|
407
|
-
constructor(
|
|
408
|
-
id: string,
|
|
409
|
-
persist: boolean | undefined,
|
|
410
|
-
readonly execute: (
|
|
411
|
-
state: TState,
|
|
412
|
-
context: { readonly signal: AbortSignal },
|
|
413
|
-
) => TState | Promise<TState>,
|
|
414
|
-
) {
|
|
415
|
-
super(id, persist);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
export function stage<TState>(definition: {
|
|
419
|
-
id: string;
|
|
420
|
-
execute: (state: TState, context: { readonly signal: AbortSignal }) => TState | Promise<TState>;
|
|
421
|
-
persist?: boolean;
|
|
422
|
-
}): Stage<TState> {
|
|
423
|
-
return new StageImplementation(definition.id, definition.persist, definition.execute);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
class InteractionImplementation<TState, TRequest, TResponse>
|
|
427
|
-
extends NodeImplementation<TState>
|
|
428
|
-
implements Interaction<TState, TRequest, TResponse>
|
|
429
|
-
{
|
|
430
|
-
readonly kind = "interaction";
|
|
431
|
-
readonly requestType?: TRequest;
|
|
432
|
-
readonly responseType?: TResponse;
|
|
433
|
-
constructor(
|
|
434
|
-
id: string,
|
|
435
|
-
persist: boolean | undefined,
|
|
436
|
-
readonly requestSchema: z.ZodType<TRequest>,
|
|
437
|
-
readonly responseSchema: z.ZodType<TResponse>,
|
|
438
|
-
readonly request: (state: TState) => TRequest,
|
|
439
|
-
readonly apply: (state: TState, response: TResponse) => TState,
|
|
440
|
-
) {
|
|
441
|
-
super(id, persist);
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
export function interaction<TState, TRequest, TResponse>(definition: {
|
|
445
|
-
id: string;
|
|
446
|
-
requestSchema: z.ZodType<TRequest>;
|
|
447
|
-
responseSchema: z.ZodType<TResponse>;
|
|
448
|
-
request: (state: TState) => TRequest;
|
|
449
|
-
apply: (state: TState, response: TResponse) => TState;
|
|
450
|
-
persist?: boolean;
|
|
451
|
-
}): Interaction<TState, TRequest, TResponse> {
|
|
452
|
-
return new InteractionImplementation(
|
|
453
|
-
definition.id,
|
|
454
|
-
definition.persist,
|
|
455
|
-
definition.requestSchema,
|
|
456
|
-
definition.responseSchema,
|
|
457
|
-
definition.request,
|
|
458
|
-
definition.apply,
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
type InteractionHandler<TRequest, TResponse> = (
|
|
463
|
-
request: TRequest,
|
|
464
|
-
context: { readonly signal: AbortSignal },
|
|
465
|
-
) => TResponse | Promise<TResponse>;
|
|
466
|
-
type RegisteredInteractionHandler = {
|
|
467
|
-
readonly interaction: Interaction<unknown, unknown, unknown>;
|
|
468
|
-
readonly handle: InteractionHandler<unknown, unknown>;
|
|
469
|
-
};
|
|
470
|
-
export interface InteractionHandlers {
|
|
471
|
-
handle<TState, TRequest, TResponse>(
|
|
472
|
-
interaction: Interaction<TState, TRequest, TResponse>,
|
|
473
|
-
handler: InteractionHandler<TRequest, TResponse>,
|
|
474
|
-
): InteractionHandlers;
|
|
475
|
-
readonly [interactionHandlersBrand]: true;
|
|
476
|
-
}
|
|
477
|
-
class InteractionHandlersImplementation implements InteractionHandlers {
|
|
478
|
-
readonly [interactionHandlersBrand] = true;
|
|
479
|
-
readonly entries: RegisteredInteractionHandler[] = [];
|
|
480
|
-
readonly #interactions = new Set<Interaction<unknown, unknown, unknown>>();
|
|
481
|
-
|
|
482
|
-
handle<TState, TRequest, TResponse>(
|
|
483
|
-
interaction: Interaction<TState, TRequest, TResponse>,
|
|
484
|
-
handler: InteractionHandler<TRequest, TResponse>,
|
|
485
|
-
): InteractionHandlers {
|
|
486
|
-
const opaque = interaction as Interaction<unknown, unknown, unknown>;
|
|
487
|
-
if (this.#interactions.has(opaque)) {
|
|
488
|
-
throw new TandemError(`Interaction '${interaction.id}' already has a handler.`);
|
|
489
|
-
}
|
|
490
|
-
this.#interactions.add(opaque);
|
|
491
|
-
this.entries.push({
|
|
492
|
-
interaction: opaque,
|
|
493
|
-
handle: handler as InteractionHandler<unknown, unknown>,
|
|
494
|
-
});
|
|
495
|
-
return this;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
export function interactions(): InteractionHandlers {
|
|
499
|
-
return new InteractionHandlersImplementation();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
interface CapabilityCompileContext<TState> {
|
|
503
|
-
readonly id: string;
|
|
504
|
-
readonly stateSchema: z.ZodType<TState>;
|
|
505
|
-
readonly callbacks: CallbackRegistry;
|
|
506
|
-
}
|
|
507
|
-
export interface Capability<TState> {
|
|
508
|
-
readonly name: string;
|
|
509
|
-
readonly [compileCapabilityBrand]: (context: CapabilityCompileContext<TState>) => object;
|
|
510
|
-
}
|
|
511
|
-
class CapabilityImplementation<TState, TRequest> implements Capability<TState> {
|
|
512
|
-
readonly requestJsonSchema: string;
|
|
513
|
-
constructor(
|
|
514
|
-
readonly name: string,
|
|
515
|
-
readonly instructions: string,
|
|
516
|
-
readonly schema: z.ZodType<TRequest>,
|
|
517
|
-
readonly validateFor:
|
|
518
|
-
| ((state: TState, request: TRequest) => readonly ValidationProblem[])
|
|
519
|
-
| undefined,
|
|
520
|
-
readonly apply: (state: TState, request: TRequest) => TState,
|
|
521
|
-
readonly summarize: (request: TRequest) => string,
|
|
522
|
-
) {
|
|
523
|
-
this.requestJsonSchema = inputJsonSchema(schema, `capability '${name}' schema`);
|
|
524
|
-
}
|
|
525
|
-
[compileCapabilityBrand]({
|
|
526
|
-
id,
|
|
527
|
-
stateSchema,
|
|
528
|
-
callbacks,
|
|
529
|
-
}: CapabilityCompileContext<TState>): object {
|
|
530
|
-
const validate = callbacks.registerSync((_, input) => issues(this.schema, input));
|
|
531
|
-
const validateFor = this.validateFor
|
|
532
|
-
? callbacks.registerSync((state, input) =>
|
|
533
|
-
validationProblems(
|
|
534
|
-
this.validateFor!(
|
|
535
|
-
parseJson(stateSchema, state, `${id} state`),
|
|
536
|
-
parseJson(this.schema, input, `${id} capability '${this.name}' request`),
|
|
537
|
-
),
|
|
538
|
-
`${id} capability '${this.name}' contextual validation`,
|
|
539
|
-
),
|
|
540
|
-
)
|
|
541
|
-
: undefined;
|
|
542
|
-
const apply = callbacks.registerSync((state, input) =>
|
|
543
|
-
serializeBoundary(
|
|
544
|
-
stateSchema,
|
|
545
|
-
this.apply(
|
|
546
|
-
parseJson(stateSchema, state, `${id} state`),
|
|
547
|
-
parseJson(this.schema, input, `${id} capability '${this.name}' request`),
|
|
548
|
-
),
|
|
549
|
-
`${id} applied state`,
|
|
550
|
-
),
|
|
551
|
-
);
|
|
552
|
-
const summary = callbacks.registerSync((_, input) =>
|
|
553
|
-
this.summarize(parseJson(this.schema, input, `${id} capability '${this.name}' request`)),
|
|
554
|
-
);
|
|
555
|
-
return {
|
|
556
|
-
name: this.name,
|
|
557
|
-
instructions: this.instructions,
|
|
558
|
-
jsonSchema: this.requestJsonSchema,
|
|
559
|
-
validateCallback: validate,
|
|
560
|
-
validateForCallback: validateFor,
|
|
561
|
-
applyCallback: apply,
|
|
562
|
-
summaryCallback: summary,
|
|
563
|
-
valueType: `${id}.capability.${this.name}`,
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
export function capability<TState, TRequest>(definition: {
|
|
568
|
-
readonly name: string;
|
|
569
|
-
readonly instructions: string;
|
|
570
|
-
readonly schema: z.ZodType<TRequest>;
|
|
571
|
-
readonly validateFor?: (state: TState, request: TRequest) => readonly ValidationProblem[];
|
|
572
|
-
readonly apply: (state: TState, request: TRequest) => TState;
|
|
573
|
-
readonly summarize: (request: TRequest) => string;
|
|
574
|
-
}): Capability<TState> {
|
|
575
|
-
requireInstructions(definition.instructions, `Capability '${definition.name}' instructions`);
|
|
576
|
-
return new CapabilityImplementation(
|
|
577
|
-
definition.name,
|
|
578
|
-
definition.instructions,
|
|
579
|
-
definition.schema,
|
|
580
|
-
definition.validateFor,
|
|
581
|
-
definition.apply,
|
|
582
|
-
definition.summarize,
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
export interface OpenAiCompatibleChatClient {
|
|
587
|
-
readonly kind: "openai-compatible";
|
|
588
|
-
readonly version: 1;
|
|
589
|
-
readonly endpoint: string;
|
|
590
|
-
readonly model: string;
|
|
591
|
-
readonly wireApi: "completions" | "responses";
|
|
592
|
-
readonly apiKeyEnvironmentVariable?: string;
|
|
593
|
-
readonly verifyModel?: boolean;
|
|
594
|
-
/** Per-attempt transport limits; omitted preserves provider defaults. */
|
|
595
|
-
readonly requestTimeoutMs?: number;
|
|
596
|
-
readonly idleTimeoutMs?: number;
|
|
597
|
-
readonly maxAttempts?: number;
|
|
598
|
-
}
|
|
599
|
-
export type ChatClient = OpenAiCompatibleChatClient;
|
|
600
|
-
export type AgentReasoning =
|
|
601
|
-
| {
|
|
602
|
-
readonly effort: "none" | "low" | "medium" | "high";
|
|
603
|
-
readonly maxTokens?: never;
|
|
604
|
-
}
|
|
605
|
-
| { readonly maxTokens: number; readonly effort?: never };
|
|
606
|
-
export interface AgentSkill {
|
|
607
|
-
readonly directory: string;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export type AgentToolName =
|
|
611
|
-
| "read_file"
|
|
612
|
-
| "ls"
|
|
613
|
-
| "grep"
|
|
614
|
-
| "write_file"
|
|
615
|
-
| "delete_file"
|
|
616
|
-
| "replace"
|
|
617
|
-
| "replace_lines"
|
|
618
|
-
| "git:ro"
|
|
619
|
-
| "shell"
|
|
620
|
-
| "web_search"
|
|
621
|
-
| "web_fetch";
|
|
622
|
-
export interface AgentCommand {
|
|
623
|
-
readonly name: string;
|
|
624
|
-
readonly description: string;
|
|
625
|
-
readonly command: string;
|
|
626
|
-
readonly arguments?: readonly string[];
|
|
627
|
-
}
|
|
628
|
-
interface AgentCommandSelection {
|
|
629
|
-
readonly [commandSelectionBrand]: object;
|
|
630
|
-
}
|
|
631
|
-
type AgentToolSelection = AgentToolName | AgentCommandSelection;
|
|
632
|
-
export interface AgentToolGroup<TState> {
|
|
633
|
-
readonly [toolGroupBrand]: (state: TState) => boolean;
|
|
634
|
-
}
|
|
635
|
-
export type AgentToolEffect =
|
|
636
|
-
| "read"
|
|
637
|
-
| "workspaceMutation"
|
|
638
|
-
| "processExecution"
|
|
639
|
-
| "lifecycleTransition"
|
|
640
|
-
| "unclassified";
|
|
641
|
-
export interface AgentToolInvocation {
|
|
642
|
-
readonly name: string;
|
|
643
|
-
readonly effect: AgentToolEffect;
|
|
644
|
-
readonly arguments: unknown;
|
|
645
|
-
}
|
|
646
|
-
export type AgentToolInterceptor<TState> = (
|
|
647
|
-
state: TState,
|
|
648
|
-
invocation: AgentToolInvocation,
|
|
649
|
-
context: { readonly signal: AbortSignal },
|
|
650
|
-
) => string | null | Promise<string | null>;
|
|
651
|
-
class AgentToolGroupImplementation<TState> implements AgentToolGroup<TState> {
|
|
652
|
-
readonly [toolGroupBrand]: (state: TState) => boolean;
|
|
653
|
-
constructor(
|
|
654
|
-
readonly predicate: ((state: TState) => boolean) | undefined,
|
|
655
|
-
readonly tools: readonly AgentToolSelection[],
|
|
656
|
-
) {
|
|
657
|
-
this[toolGroupBrand] = predicate ?? (() => true);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
function createToolGroup<TState>(
|
|
661
|
-
predicate: ((state: TState) => boolean) | undefined,
|
|
662
|
-
tools: readonly AgentToolSelection[],
|
|
663
|
-
): AgentToolGroup<TState> {
|
|
664
|
-
if (tools.length === 0) {
|
|
665
|
-
throw new TandemError("An agent tool group cannot be empty.");
|
|
666
|
-
}
|
|
667
|
-
const seen = new Set<AgentToolSelection>();
|
|
668
|
-
for (const tool of tools) {
|
|
669
|
-
if (typeof tool !== "string" && !(commandSelectionBrand in tool)) {
|
|
670
|
-
throw new TandemError("Agent tools must be built-in names or workspace.commands.");
|
|
671
|
-
}
|
|
672
|
-
if (seen.has(tool)) {
|
|
673
|
-
throw new TandemError("An agent tool group cannot select a tool twice.");
|
|
674
|
-
}
|
|
675
|
-
seen.add(tool);
|
|
676
|
-
}
|
|
677
|
-
return new AgentToolGroupImplementation(predicate, tools);
|
|
678
|
-
}
|
|
679
|
-
export const agentTools = {
|
|
680
|
-
always: (...tools: readonly AgentToolSelection[]): AgentToolGroup<never> =>
|
|
681
|
-
createToolGroup(undefined, tools),
|
|
682
|
-
when: <TState>(
|
|
683
|
-
predicate: (state: TState) => boolean,
|
|
684
|
-
...tools: readonly AgentToolSelection[]
|
|
685
|
-
): AgentToolGroup<TState> => createToolGroup(predicate, tools),
|
|
686
|
-
};
|
|
687
|
-
export interface AgentWorkspaceConfiguration<TState> {
|
|
688
|
-
readonly [workspaceBrand]: (state: TState) => TState;
|
|
689
|
-
}
|
|
690
|
-
export interface AgentWorkspace<TState> {
|
|
691
|
-
readonly commands: AgentCommandSelection;
|
|
692
|
-
withTools(
|
|
693
|
-
groups: readonly (AgentToolGroup<TState> | AgentToolGroup<never>)[],
|
|
694
|
-
options?: { readonly interceptTool?: AgentToolInterceptor<TState> },
|
|
695
|
-
): AgentWorkspaceConfiguration<TState>;
|
|
696
|
-
}
|
|
697
|
-
class AgentWorkspaceImplementation<TState> implements AgentWorkspace<TState> {
|
|
698
|
-
readonly commands: AgentCommandSelection;
|
|
699
|
-
readonly commandSource:
|
|
700
|
-
| readonly AgentCommand[]
|
|
701
|
-
| ((state: TState) => readonly AgentCommand[])
|
|
702
|
-
| undefined;
|
|
703
|
-
constructor(
|
|
704
|
-
readonly path: (state: TState) => string,
|
|
705
|
-
commandSource:
|
|
706
|
-
| readonly AgentCommand[]
|
|
707
|
-
| ((state: TState) => readonly AgentCommand[])
|
|
708
|
-
| undefined,
|
|
709
|
-
) {
|
|
710
|
-
this.commandSource =
|
|
711
|
-
typeof commandSource === "function" || commandSource === undefined
|
|
712
|
-
? commandSource
|
|
713
|
-
: commandSource.map(copyAgentCommand);
|
|
714
|
-
this.commands = { [commandSelectionBrand]: this };
|
|
715
|
-
}
|
|
716
|
-
withTools(
|
|
717
|
-
groups: readonly (AgentToolGroup<TState> | AgentToolGroup<never>)[],
|
|
718
|
-
options?: { readonly interceptTool?: AgentToolInterceptor<TState> },
|
|
719
|
-
): AgentWorkspaceConfiguration<TState> {
|
|
720
|
-
if (groups.length === 0) {
|
|
721
|
-
throw new TandemError("An agent workspace requires tool groups.");
|
|
722
|
-
}
|
|
723
|
-
const implementations = groups.map((group) => {
|
|
724
|
-
if (!(group instanceof AgentToolGroupImplementation)) {
|
|
725
|
-
throw new TandemError("Agent tool groups must be created by agentTools.");
|
|
726
|
-
}
|
|
727
|
-
return group as AgentToolGroupImplementation<TState>;
|
|
728
|
-
});
|
|
729
|
-
if (options?.interceptTool !== undefined && typeof options.interceptTool !== "function") {
|
|
730
|
-
throw new TandemError("Workspace tool interceptor must be a function.");
|
|
731
|
-
}
|
|
732
|
-
return new AgentWorkspaceConfigurationImplementation(
|
|
733
|
-
this,
|
|
734
|
-
implementations,
|
|
735
|
-
options?.interceptTool,
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
class AgentWorkspaceConfigurationImplementation<
|
|
740
|
-
TState,
|
|
741
|
-
> implements AgentWorkspaceConfiguration<TState> {
|
|
742
|
-
readonly [workspaceBrand]!: (state: TState) => TState;
|
|
743
|
-
constructor(
|
|
744
|
-
readonly workspace: AgentWorkspaceImplementation<TState>,
|
|
745
|
-
readonly groups: readonly AgentToolGroupImplementation<TState>[],
|
|
746
|
-
readonly interceptTool: AgentToolInterceptor<TState> | undefined,
|
|
747
|
-
) {}
|
|
748
|
-
}
|
|
749
|
-
export function agentWorkspace<TState>(definition: {
|
|
750
|
-
readonly path: (state: TState) => string;
|
|
751
|
-
readonly commands?: readonly AgentCommand[] | ((state: TState) => readonly AgentCommand[]);
|
|
752
|
-
}): AgentWorkspace<TState> {
|
|
753
|
-
if (typeof definition.path !== "function") {
|
|
754
|
-
throw new TandemError("Workspace path is required.");
|
|
755
|
-
}
|
|
756
|
-
if (definition.commands !== undefined && typeof definition.commands !== "function") {
|
|
757
|
-
validateAgentCommands(definition.commands, "Workspace commands");
|
|
758
|
-
}
|
|
759
|
-
return new AgentWorkspaceImplementation(definition.path, definition.commands);
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function copyAgentCommand(command: AgentCommand): AgentCommand {
|
|
763
|
-
return command.arguments === undefined
|
|
764
|
-
? { ...command }
|
|
765
|
-
: {
|
|
766
|
-
...command,
|
|
767
|
-
arguments: [...command.arguments],
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function validateAgentCommands(
|
|
772
|
-
commands: unknown,
|
|
773
|
-
context: string,
|
|
774
|
-
): asserts commands is readonly AgentCommand[] {
|
|
775
|
-
if (!Array.isArray(commands)) {
|
|
776
|
-
throw new TandemError(`${context} must be an array.`);
|
|
777
|
-
}
|
|
778
|
-
for (const [commandIndex, command] of commands.entries()) {
|
|
779
|
-
const commandContext = `${context}[${commandIndex}]`;
|
|
780
|
-
if (typeof command !== "object" || command === null) {
|
|
781
|
-
throw new TandemError(`${commandContext} must be a command.`);
|
|
782
|
-
}
|
|
783
|
-
const candidate = command as Partial<AgentCommand>;
|
|
784
|
-
if (typeof candidate.name !== "string" || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(candidate.name)) {
|
|
785
|
-
throw new TandemError(`${commandContext}.name must be a valid tool name.`);
|
|
786
|
-
}
|
|
787
|
-
if (typeof candidate.description !== "string" || candidate.description.trim().length === 0) {
|
|
788
|
-
throw new TandemError(`${commandContext}.description must be non-blank.`);
|
|
789
|
-
}
|
|
790
|
-
if (typeof candidate.command !== "string" || candidate.command.trim().length === 0) {
|
|
791
|
-
throw new TandemError(`${commandContext}.command must be non-blank.`);
|
|
792
|
-
}
|
|
793
|
-
if (candidate.arguments !== undefined && !Array.isArray(candidate.arguments)) {
|
|
794
|
-
throw new TandemError(`${commandContext}.arguments must be an array of strings.`);
|
|
795
|
-
}
|
|
796
|
-
const argumentList = candidate.arguments ?? [];
|
|
797
|
-
if (argumentList.length > 16) {
|
|
798
|
-
throw new TandemError(`${commandContext}.arguments accepts at most 16 arguments.`);
|
|
799
|
-
}
|
|
800
|
-
for (const [argumentIndex, argument] of argumentList.entries()) {
|
|
801
|
-
const argumentContext = `${commandContext}.arguments[${argumentIndex}]`;
|
|
802
|
-
if (typeof argument !== "string") {
|
|
803
|
-
throw new TandemError(`${argumentContext} must be a string.`);
|
|
804
|
-
}
|
|
805
|
-
if (argument.trim().length === 0) {
|
|
806
|
-
throw new TandemError(`${argumentContext} must not be blank.`);
|
|
807
|
-
}
|
|
808
|
-
if (argument.length > 200) {
|
|
809
|
-
throw new TandemError(`${argumentContext} must be at most 200 characters.`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
export function skill(definition: { readonly directory: string }): AgentSkill {
|
|
815
|
-
if (typeof definition.directory !== "string" || definition.directory.trim().length === 0) {
|
|
816
|
-
throw new TandemError("Skill directory must be a non-blank string.");
|
|
817
|
-
}
|
|
818
|
-
return { directory: definition.directory };
|
|
819
|
-
}
|
|
820
|
-
export interface AgentDefinition<TState, TOutput = never> {
|
|
821
|
-
readonly id: string;
|
|
822
|
-
readonly instructions: string;
|
|
823
|
-
readonly client: ChatClient;
|
|
824
|
-
readonly message: (state: TState) => string;
|
|
825
|
-
readonly output?:
|
|
826
|
-
| {
|
|
827
|
-
readonly instructions: string;
|
|
828
|
-
readonly schema: z.ZodType<TOutput>;
|
|
829
|
-
readonly validateFor?: (state: TState, output: TOutput) => readonly ValidationProblem[];
|
|
830
|
-
readonly apply: (state: TState, output: TOutput) => TState;
|
|
831
|
-
}
|
|
832
|
-
| {
|
|
833
|
-
readonly instructions: string;
|
|
834
|
-
readonly raw: true;
|
|
835
|
-
readonly parse: (response: string) => TOutput;
|
|
836
|
-
readonly validateFor?: (state: TState, output: TOutput) => readonly ValidationProblem[];
|
|
837
|
-
readonly apply: (state: TState, output: TOutput) => TState;
|
|
838
|
-
};
|
|
839
|
-
readonly capabilities?: readonly Capability<TState>[];
|
|
840
|
-
readonly skills?: readonly AgentSkill[];
|
|
841
|
-
readonly workspace?: AgentWorkspaceConfiguration<TState>;
|
|
842
|
-
readonly temperature?: number;
|
|
843
|
-
readonly maxOutputTokens?: number;
|
|
844
|
-
readonly reasoning?: AgentReasoning;
|
|
845
|
-
readonly continueSession?: boolean;
|
|
846
|
-
readonly checkpoint?: {
|
|
847
|
-
readonly contextWindowTokens: number;
|
|
848
|
-
readonly maxOutputTokens: number;
|
|
849
|
-
readonly checkpointAtPercent: number;
|
|
850
|
-
readonly capability: Capability<TState>;
|
|
851
|
-
readonly instructions: string;
|
|
852
|
-
readonly message: (state: TState, currentContextTokens: number) => string;
|
|
853
|
-
readonly session?: "retain" | "reset";
|
|
854
|
-
readonly disableCompaction?: boolean;
|
|
855
|
-
};
|
|
856
|
-
readonly timeoutMs?: number;
|
|
857
|
-
readonly persist?: boolean;
|
|
858
|
-
}
|
|
859
|
-
class AgentImplementation<TState, TOutput>
|
|
860
|
-
extends NodeImplementation<TState>
|
|
861
|
-
implements Agent<TState>
|
|
862
|
-
{
|
|
863
|
-
readonly kind = "agent";
|
|
864
|
-
constructor(
|
|
865
|
-
id: string,
|
|
866
|
-
persist: boolean | undefined,
|
|
867
|
-
readonly instructions: string,
|
|
868
|
-
readonly client: ChatClient,
|
|
869
|
-
readonly message: (state: TState) => string,
|
|
870
|
-
readonly output:
|
|
871
|
-
| {
|
|
872
|
-
instructions: string;
|
|
873
|
-
schema: z.ZodType<TOutput>;
|
|
874
|
-
validateFor?: (state: TState, output: TOutput) => readonly ValidationProblem[];
|
|
875
|
-
apply: (state: TState, output: TOutput) => TState;
|
|
876
|
-
}
|
|
877
|
-
| {
|
|
878
|
-
instructions: string;
|
|
879
|
-
raw: true;
|
|
880
|
-
parse: (response: string) => TOutput;
|
|
881
|
-
validateFor?: (state: TState, output: TOutput) => readonly ValidationProblem[];
|
|
882
|
-
apply: (state: TState, output: TOutput) => TState;
|
|
883
|
-
}
|
|
884
|
-
| undefined,
|
|
885
|
-
readonly granted: readonly Capability<TState>[],
|
|
886
|
-
readonly skills: readonly AgentSkill[],
|
|
887
|
-
readonly workspace: AgentWorkspaceConfiguration<TState> | undefined,
|
|
888
|
-
readonly temperature: number | undefined,
|
|
889
|
-
readonly maxOutputTokens: number | undefined,
|
|
890
|
-
readonly reasoning: AgentReasoning | undefined,
|
|
891
|
-
readonly continueSession: boolean,
|
|
892
|
-
readonly checkpoint: AgentDefinition<TState>["checkpoint"],
|
|
893
|
-
readonly timeoutMs?: number,
|
|
894
|
-
) {
|
|
895
|
-
super(id, persist);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
export function agent<TState, TOutput = never>(
|
|
899
|
-
definition: AgentDefinition<TState, TOutput>,
|
|
900
|
-
): Agent<TState> {
|
|
901
|
-
requireInstructions(definition.instructions, `Agent '${definition.id}' instructions`);
|
|
902
|
-
const reasoningEffort = definition.reasoning?.effort;
|
|
903
|
-
const reasoningMaxTokens = definition.reasoning?.maxTokens;
|
|
904
|
-
if (
|
|
905
|
-
definition.reasoning &&
|
|
906
|
-
(reasoningEffort !== undefined) === (reasoningMaxTokens !== undefined)
|
|
907
|
-
) {
|
|
908
|
-
throw new TandemError(
|
|
909
|
-
`Agent '${definition.id}' reasoning must specify exactly one of effort or maxTokens.`,
|
|
910
|
-
);
|
|
911
|
-
}
|
|
912
|
-
if (
|
|
913
|
-
reasoningEffort !== undefined &&
|
|
914
|
-
!["none", "low", "medium", "high"].includes(reasoningEffort)
|
|
915
|
-
) {
|
|
916
|
-
throw new TandemError(`Agent '${definition.id}' has an invalid reasoning effort.`);
|
|
917
|
-
}
|
|
918
|
-
if (reasoningMaxTokens !== undefined) {
|
|
919
|
-
if (
|
|
920
|
-
!Number.isSafeInteger(reasoningMaxTokens) ||
|
|
921
|
-
reasoningMaxTokens < 1024 ||
|
|
922
|
-
reasoningMaxTokens > 2_147_483_647
|
|
923
|
-
) {
|
|
924
|
-
throw new TandemError(
|
|
925
|
-
`Agent '${definition.id}' reasoning maxTokens must be a 32-bit integer of at least 1024.`,
|
|
926
|
-
);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
if (definition.output) {
|
|
930
|
-
requireInstructions(
|
|
931
|
-
definition.output.instructions,
|
|
932
|
-
`Agent '${definition.id}' output instructions`,
|
|
933
|
-
);
|
|
934
|
-
if ("raw" in definition.output) {
|
|
935
|
-
if (definition.output.raw !== true) {
|
|
936
|
-
throw new TandemError(`Agent '${definition.id}' output raw must be true.`);
|
|
937
|
-
}
|
|
938
|
-
if ("schema" in definition.output) {
|
|
939
|
-
throw new TandemError(
|
|
940
|
-
`Agent '${definition.id}' output schema is forbidden for raw output.`,
|
|
941
|
-
);
|
|
942
|
-
}
|
|
943
|
-
if (typeof definition.output.parse !== "function") {
|
|
944
|
-
throw new TandemError(`Agent '${definition.id}' raw output requires a parse function.`);
|
|
945
|
-
}
|
|
946
|
-
} else if ("parse" in definition.output) {
|
|
947
|
-
throw new TandemError(
|
|
948
|
-
`Agent '${definition.id}' output parse requires raw output mode.`,
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
const capabilities = definition.capabilities ?? [];
|
|
953
|
-
const names = new Set<string>();
|
|
954
|
-
for (const item of capabilities) {
|
|
955
|
-
if (names.has(item.name)) {
|
|
956
|
-
throw new TandemError(`Agent '${definition.id}' has duplicate capability '${item.name}'.`);
|
|
957
|
-
} else {
|
|
958
|
-
names.add(item.name);
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
const skills = definition.skills ?? [];
|
|
962
|
-
const skillDirectories = new Set<string>();
|
|
963
|
-
for (const item of skills) {
|
|
964
|
-
if (typeof item?.directory !== "string" || item.directory.trim().length === 0) {
|
|
965
|
-
throw new TandemError(`Agent '${definition.id}' has a skill with an invalid directory.`);
|
|
966
|
-
}
|
|
967
|
-
if (!skillDirectories.add(item.directory)) {
|
|
968
|
-
throw new TandemError(
|
|
969
|
-
`Agent '${definition.id}' has the skill directory '${item.directory}' more than once.`,
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
if (
|
|
974
|
-
definition.temperature !== undefined &&
|
|
975
|
-
(!Number.isFinite(definition.temperature) ||
|
|
976
|
-
definition.temperature < 0 ||
|
|
977
|
-
definition.temperature > 2)
|
|
978
|
-
) {
|
|
979
|
-
throw new TandemError(`Agent '${definition.id}' temperature must be between 0 and 2.`);
|
|
980
|
-
}
|
|
981
|
-
if (
|
|
982
|
-
definition.maxOutputTokens !== undefined &&
|
|
983
|
-
(!Number.isSafeInteger(definition.maxOutputTokens) ||
|
|
984
|
-
definition.maxOutputTokens <= 0 ||
|
|
985
|
-
definition.maxOutputTokens > 2_147_483_647)
|
|
986
|
-
) {
|
|
987
|
-
throw new TandemError(
|
|
988
|
-
`Agent '${definition.id}' maxOutputTokens must be a positive 32-bit integer.`,
|
|
989
|
-
);
|
|
990
|
-
}
|
|
991
|
-
if (definition.checkpoint) {
|
|
992
|
-
const checkpoint = definition.checkpoint;
|
|
993
|
-
if (
|
|
994
|
-
!Number.isSafeInteger(checkpoint.contextWindowTokens) ||
|
|
995
|
-
checkpoint.contextWindowTokens <= 0 ||
|
|
996
|
-
checkpoint.contextWindowTokens > 2_147_483_647
|
|
997
|
-
) {
|
|
998
|
-
throw new TandemError(
|
|
999
|
-
`Agent '${definition.id}' checkpoint contextWindowTokens must be a positive 32-bit integer.`,
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
if (
|
|
1003
|
-
!Number.isSafeInteger(checkpoint.maxOutputTokens) ||
|
|
1004
|
-
checkpoint.maxOutputTokens <= 0 ||
|
|
1005
|
-
checkpoint.maxOutputTokens > 2_147_483_647 ||
|
|
1006
|
-
checkpoint.maxOutputTokens >= checkpoint.contextWindowTokens
|
|
1007
|
-
) {
|
|
1008
|
-
throw new TandemError(
|
|
1009
|
-
`Agent '${definition.id}' checkpoint maxOutputTokens must be a positive 32-bit integer smaller than contextWindowTokens.`,
|
|
1010
|
-
);
|
|
1011
|
-
}
|
|
1012
|
-
if (
|
|
1013
|
-
!Number.isSafeInteger(checkpoint.checkpointAtPercent) ||
|
|
1014
|
-
checkpoint.checkpointAtPercent <= 0 ||
|
|
1015
|
-
checkpoint.checkpointAtPercent >= 100
|
|
1016
|
-
) {
|
|
1017
|
-
throw new TandemError(
|
|
1018
|
-
`Agent '${definition.id}' checkpoint checkpointAtPercent must be between 1 and 99.`,
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
if (!capabilities.includes(checkpoint.capability)) {
|
|
1022
|
-
throw new TandemError(
|
|
1023
|
-
`Agent '${definition.id}' checkpoint capability must be attached to the agent.`,
|
|
1024
|
-
);
|
|
1025
|
-
}
|
|
1026
|
-
requireInstructions(
|
|
1027
|
-
checkpoint.instructions,
|
|
1028
|
-
`Agent '${definition.id}' checkpoint instructions`,
|
|
1029
|
-
);
|
|
1030
|
-
if (
|
|
1031
|
-
checkpoint.session !== undefined &&
|
|
1032
|
-
checkpoint.session !== "retain" &&
|
|
1033
|
-
checkpoint.session !== "reset"
|
|
1034
|
-
) {
|
|
1035
|
-
throw new TandemError(
|
|
1036
|
-
`Agent '${definition.id}' checkpoint session must be 'retain' or 'reset'.`,
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
if (
|
|
1040
|
-
checkpoint.disableCompaction !== undefined &&
|
|
1041
|
-
typeof checkpoint.disableCompaction !== "boolean"
|
|
1042
|
-
) {
|
|
1043
|
-
throw new TandemError(
|
|
1044
|
-
`Agent '${definition.id}' checkpoint disableCompaction must be a boolean.`,
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
return new AgentImplementation(
|
|
1049
|
-
definition.id,
|
|
1050
|
-
definition.persist,
|
|
1051
|
-
definition.instructions,
|
|
1052
|
-
definition.client,
|
|
1053
|
-
definition.message,
|
|
1054
|
-
definition.output,
|
|
1055
|
-
capabilities,
|
|
1056
|
-
skills,
|
|
1057
|
-
definition.workspace,
|
|
1058
|
-
definition.temperature,
|
|
1059
|
-
definition.maxOutputTokens,
|
|
1060
|
-
definition.reasoning,
|
|
1061
|
-
definition.continueSession ?? false,
|
|
1062
|
-
definition.checkpoint,
|
|
1063
|
-
definition.timeoutMs,
|
|
1064
|
-
);
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
type ParallelBranches<TState> = Readonly<Record<string, Stage<TState> | Agent<TState>>>;
|
|
1068
|
-
type ParallelDefinition<TState, TBranches extends ParallelBranches<TState>> = {
|
|
1069
|
-
readonly id: string;
|
|
1070
|
-
readonly branches: TBranches;
|
|
1071
|
-
readonly merge: (
|
|
1072
|
-
baseline: TState,
|
|
1073
|
-
results: { readonly [K in keyof TBranches]: TState },
|
|
1074
|
-
) => TState;
|
|
1075
|
-
/** Maximum active branches per invocation; omitted means all branches. */
|
|
1076
|
-
readonly max?: number;
|
|
1077
|
-
readonly persist?: boolean;
|
|
1078
|
-
};
|
|
1079
|
-
class ParallelImplementation<TState, TBranches extends ParallelBranches<TState>>
|
|
1080
|
-
extends NodeImplementation<TState>
|
|
1081
|
-
implements Parallel<TState>
|
|
1082
|
-
{
|
|
1083
|
-
readonly kind = "parallel";
|
|
1084
|
-
constructor(
|
|
1085
|
-
id: string,
|
|
1086
|
-
persist: boolean | undefined,
|
|
1087
|
-
readonly branches: TBranches,
|
|
1088
|
-
readonly max: number | undefined,
|
|
1089
|
-
readonly merge: (
|
|
1090
|
-
baseline: TState,
|
|
1091
|
-
results: { readonly [K in keyof TBranches]: TState },
|
|
1092
|
-
) => TState,
|
|
1093
|
-
) {
|
|
1094
|
-
super(id, persist);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
export function parallel<TState>(): <const TBranches extends ParallelBranches<TState>>(
|
|
1098
|
-
definition: ParallelDefinition<TState, TBranches>,
|
|
1099
|
-
) => Parallel<TState>;
|
|
1100
|
-
export function parallel<TState, const TBranches extends ParallelBranches<TState>>(
|
|
1101
|
-
definition: ParallelDefinition<TState, TBranches>,
|
|
1102
|
-
): Parallel<TState>;
|
|
1103
|
-
export function parallel<TState>(
|
|
1104
|
-
definition?: ParallelDefinition<TState, ParallelBranches<TState>>,
|
|
1105
|
-
):
|
|
1106
|
-
| Parallel<TState>
|
|
1107
|
-
| (<const TBranches extends ParallelBranches<TState>>(
|
|
1108
|
-
value: ParallelDefinition<TState, TBranches>,
|
|
1109
|
-
) => Parallel<TState>) {
|
|
1110
|
-
if (!definition) {
|
|
1111
|
-
return (value) => createParallel(value);
|
|
1112
|
-
}
|
|
1113
|
-
return createParallel(definition);
|
|
1114
|
-
}
|
|
1115
|
-
function createParallel<TState, TBranches extends ParallelBranches<TState>>(
|
|
1116
|
-
definition: ParallelDefinition<TState, TBranches>,
|
|
1117
|
-
): Parallel<TState> {
|
|
1118
|
-
if (
|
|
1119
|
-
definition.max !== undefined &&
|
|
1120
|
-
(!Number.isInteger(definition.max) || definition.max < 1 || definition.max > 2147483647)
|
|
1121
|
-
) {
|
|
1122
|
-
throw new TandemError(
|
|
1123
|
-
`Parallel group '${definition.id}' max must be a positive 32-bit integer.`,
|
|
1124
|
-
);
|
|
1125
|
-
}
|
|
1126
|
-
const entries = Object.entries(definition.branches);
|
|
1127
|
-
if (entries.length < 2) {
|
|
1128
|
-
throw new TandemError(`Parallel group '${definition.id}' requires at least two branches.`);
|
|
1129
|
-
}
|
|
1130
|
-
const participants = new Set(entries.map(([, participant]) => participant));
|
|
1131
|
-
if (participants.size !== entries.length) {
|
|
1132
|
-
throw new TandemError(
|
|
1133
|
-
`Parallel group '${definition.id}' must own a distinct participant per branch.`,
|
|
1134
|
-
);
|
|
1135
|
-
}
|
|
1136
|
-
return new ParallelImplementation(
|
|
1137
|
-
definition.id,
|
|
1138
|
-
definition.persist,
|
|
1139
|
-
definition.branches,
|
|
1140
|
-
definition.max,
|
|
1141
|
-
definition.merge,
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
class TerminalImplementation<TState>
|
|
1146
|
-
extends NodeImplementation<TState>
|
|
1147
|
-
implements Terminal<TState>
|
|
1148
|
-
{
|
|
1149
|
-
readonly kind = "terminal";
|
|
1150
|
-
constructor(
|
|
1151
|
-
id: string,
|
|
1152
|
-
persist: boolean | undefined,
|
|
1153
|
-
readonly failed: boolean,
|
|
1154
|
-
readonly summary: (state: TState) => string,
|
|
1155
|
-
) {
|
|
1156
|
-
super(id, persist);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
export function output<TState>(definition: {
|
|
1160
|
-
id: string;
|
|
1161
|
-
summary: (state: TState) => string;
|
|
1162
|
-
failed?: boolean;
|
|
1163
|
-
persist?: boolean;
|
|
1164
|
-
}): Terminal<TState> {
|
|
1165
|
-
return new TerminalImplementation(
|
|
1166
|
-
definition.id,
|
|
1167
|
-
definition.persist,
|
|
1168
|
-
definition.failed ?? false,
|
|
1169
|
-
definition.summary,
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
export interface OrdinaryRoute<TState> {
|
|
1174
|
-
readonly from: Stage<TState> | Interaction<TState, unknown, unknown>;
|
|
1175
|
-
readonly to: Node<TState>;
|
|
1176
|
-
readonly label: string;
|
|
1177
|
-
readonly outcome?: never;
|
|
1178
|
-
readonly when?: (state: TState) => boolean;
|
|
1179
|
-
}
|
|
1180
|
-
export interface StandardOutcomeRoute<TState> {
|
|
1181
|
-
readonly from: Agent<TState> | Parallel<TState>;
|
|
1182
|
-
readonly to: Node<TState>;
|
|
1183
|
-
readonly label: string;
|
|
1184
|
-
readonly outcome: "success" | "failed";
|
|
1185
|
-
readonly when?: (state: TState) => boolean;
|
|
1186
|
-
}
|
|
1187
|
-
export type Route<TState> = OrdinaryRoute<TState> | StandardOutcomeRoute<TState>;
|
|
1188
|
-
export function route<TState>(definition: OrdinaryRoute<TState>): OrdinaryRoute<TState>;
|
|
1189
|
-
export function route<TState>(
|
|
1190
|
-
definition: StandardOutcomeRoute<TState>,
|
|
1191
|
-
): StandardOutcomeRoute<TState>;
|
|
1192
|
-
export function route<TState>(definition: Route<TState>): Route<TState> {
|
|
1193
|
-
return definition;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
export interface Pipeline<TState> {
|
|
1197
|
-
readonly name: string;
|
|
1198
|
-
readonly state: z.ZodType<TState>;
|
|
1199
|
-
readonly nodes: readonly Node<TState>[];
|
|
1200
|
-
readonly start: Exclude<Node<TState>, Terminal<TState>>;
|
|
1201
|
-
readonly routes: readonly Route<TState>[];
|
|
1202
|
-
readonly outputs: readonly Terminal<TState>[];
|
|
1203
|
-
readonly persist: boolean;
|
|
1204
|
-
}
|
|
1205
|
-
export function pipeline<TState>(definition: {
|
|
1206
|
-
name: string;
|
|
1207
|
-
state: z.ZodType<TState>;
|
|
1208
|
-
nodes: readonly Node<NoInfer<TState>>[];
|
|
1209
|
-
start: Exclude<Node<NoInfer<TState>>, Terminal<NoInfer<TState>>>;
|
|
1210
|
-
routes: readonly Route<NoInfer<TState>>[];
|
|
1211
|
-
outputs: readonly Terminal<NoInfer<TState>>[];
|
|
1212
|
-
persist?: boolean;
|
|
1213
|
-
}): Pipeline<TState> {
|
|
1214
|
-
const start = definition.start as Node<TState>;
|
|
1215
|
-
const members = new Set<Node<TState>>(definition.nodes);
|
|
1216
|
-
if (members.size !== definition.nodes.length) {
|
|
1217
|
-
throw new Error("Pipeline nodes must contain each participant object exactly once.");
|
|
1218
|
-
}
|
|
1219
|
-
const ids = new Set(definition.nodes.map((node) => node.id));
|
|
1220
|
-
if (ids.size !== definition.nodes.length) {
|
|
1221
|
-
throw new Error("Pipeline node IDs must be unique.");
|
|
1222
|
-
}
|
|
1223
|
-
const ownedParticipants = new Set<Stage<TState> | Agent<TState>>();
|
|
1224
|
-
for (const node of definition.nodes) {
|
|
1225
|
-
if (node.kind !== "parallel") {
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
const parallelNode = node as ParallelImplementation<TState, ParallelBranches<TState>>;
|
|
1229
|
-
for (const [branchId, participant] of Object.entries(parallelNode.branches)) {
|
|
1230
|
-
if (branchId.trim().length === 0) {
|
|
1231
|
-
throw new Error(`Parallel group '${node.id}' contains a blank branch ID.`);
|
|
1232
|
-
}
|
|
1233
|
-
if (members.has(participant)) {
|
|
1234
|
-
throw new Error(
|
|
1235
|
-
`Parallel branch participant '${participant.id}' cannot also be a parent pipeline node.`,
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
1238
|
-
if (!ownedParticipants.add(participant)) {
|
|
1239
|
-
throw new Error(`Parallel branch participant '${participant.id}' is owned more than once.`);
|
|
1240
|
-
}
|
|
1241
|
-
if (ids.has(participant.id)) {
|
|
1242
|
-
throw new Error(`Pipeline participant ID '${participant.id}' must be globally unique.`);
|
|
1243
|
-
}
|
|
1244
|
-
ids.add(participant.id);
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
if (!members.has(definition.start)) {
|
|
1248
|
-
throw new Error(
|
|
1249
|
-
`Pipeline start '${definition.start.id}' must be the registered participant object.`,
|
|
1250
|
-
);
|
|
1251
|
-
}
|
|
1252
|
-
if (start.kind === "terminal") {
|
|
1253
|
-
throw new Error(`Pipeline start '${start.id}' cannot be a terminal.`);
|
|
1254
|
-
}
|
|
1255
|
-
for (const item of definition.routes) {
|
|
1256
|
-
if (!members.has(item.from) || !members.has(item.to)) {
|
|
1257
|
-
throw new Error(`Route '${item.label}' endpoints must be registered participant objects.`);
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
const unconditionalRoutes = new Map<string, Route<TState>>();
|
|
1261
|
-
for (const item of definition.routes) {
|
|
1262
|
-
if (item.when) {
|
|
1263
|
-
continue;
|
|
1264
|
-
}
|
|
1265
|
-
const key = `${item.from.id}\u0000${item.outcome ?? "default"}`;
|
|
1266
|
-
const existing = unconditionalRoutes.get(key);
|
|
1267
|
-
if (existing) {
|
|
1268
|
-
throw new Error(
|
|
1269
|
-
`Routes '${existing.label}' and '${item.label}' are both unconditional from '${item.from.id}'.`,
|
|
1270
|
-
);
|
|
1271
|
-
}
|
|
1272
|
-
unconditionalRoutes.set(key, item);
|
|
1273
|
-
}
|
|
1274
|
-
if (new Set(definition.outputs).size !== definition.outputs.length) {
|
|
1275
|
-
throw new Error("Pipeline outputs must contain each terminal exactly once.");
|
|
1276
|
-
}
|
|
1277
|
-
for (const item of definition.outputs) {
|
|
1278
|
-
if (!members.has(item)) {
|
|
1279
|
-
throw new Error(`Output '${item.id}' must be the registered participant object.`);
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
const reachable = new Set<Node<TState>>([start]);
|
|
1283
|
-
const pending: Node<TState>[] = [start];
|
|
1284
|
-
while (pending.length > 0) {
|
|
1285
|
-
const source = pending.pop()!;
|
|
1286
|
-
for (const item of definition.routes) {
|
|
1287
|
-
if (item.from === source && !reachable.has(item.to)) {
|
|
1288
|
-
reachable.add(item.to);
|
|
1289
|
-
pending.push(item.to);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
const outputs = new Set<Node<TState>>(definition.outputs);
|
|
1294
|
-
for (const node of reachable) {
|
|
1295
|
-
if (node.kind === "terminal" && !outputs.has(node)) {
|
|
1296
|
-
throw new Error(`Reachable terminal '${node.id}' must be listed in outputs.`);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
for (const item of definition.outputs) {
|
|
1300
|
-
if (!reachable.has(item)) {
|
|
1301
|
-
throw new Error(`Output '${item.id}' must be reachable from start '${start.id}'.`);
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
return { ...definition, persist: definition.persist ?? false };
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
export interface PipelineInspection {
|
|
1308
|
-
readonly name: string;
|
|
1309
|
-
readonly stateSchema: unknown;
|
|
1310
|
-
readonly start: string;
|
|
1311
|
-
readonly persist: boolean;
|
|
1312
|
-
readonly nodes: readonly InspectedNode[];
|
|
1313
|
-
readonly routes: readonly InspectedRoute[];
|
|
1314
|
-
readonly outputs: readonly string[];
|
|
1315
|
-
}
|
|
1316
|
-
export interface InspectedRoute {
|
|
1317
|
-
readonly id: string;
|
|
1318
|
-
readonly source: string;
|
|
1319
|
-
readonly target: string;
|
|
1320
|
-
readonly label: string;
|
|
1321
|
-
readonly order: number;
|
|
1322
|
-
readonly outcome?: "success" | "failed";
|
|
1323
|
-
readonly conditional: boolean;
|
|
1324
|
-
}
|
|
1325
|
-
export interface InspectedNode {
|
|
1326
|
-
readonly id: string;
|
|
1327
|
-
readonly kind: "stage" | "interaction" | "agent" | "parallel" | "completion" | "failure";
|
|
1328
|
-
readonly persist?: boolean;
|
|
1329
|
-
readonly interaction?: { readonly requestSchema: unknown; readonly responseSchema: unknown };
|
|
1330
|
-
readonly agent?: {
|
|
1331
|
-
readonly capabilities: readonly { readonly name: string; readonly requestSchema: unknown }[];
|
|
1332
|
-
readonly outputSchema?: unknown;
|
|
1333
|
-
readonly workspace: boolean;
|
|
1334
|
-
};
|
|
1335
|
-
readonly max?: number;
|
|
1336
|
-
readonly branches?: readonly { readonly id: string; readonly participant: InspectedNode }[];
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
/** Projects an instantiated pipeline without compiling, registering, or invoking callbacks. */
|
|
1340
|
-
export function inspectPipeline<TState>(graph: Pipeline<TState>): PipelineInspection {
|
|
1341
|
-
const inspectNode = (node: Node<TState>): InspectedNode => {
|
|
1342
|
-
const implementation = node as NodeImplementation<TState>;
|
|
1343
|
-
const persist = implementation.persist;
|
|
1344
|
-
if (node.kind === "interaction") {
|
|
1345
|
-
const interaction = implementation as InteractionImplementation<TState, unknown, unknown>;
|
|
1346
|
-
return {
|
|
1347
|
-
id: node.id,
|
|
1348
|
-
kind: "interaction",
|
|
1349
|
-
persist,
|
|
1350
|
-
interaction: {
|
|
1351
|
-
requestSchema: z.toJSONSchema(interaction.requestSchema, { io: "input" }),
|
|
1352
|
-
responseSchema: z.toJSONSchema(interaction.responseSchema, { io: "input" }),
|
|
1353
|
-
},
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
if (node.kind === "agent") {
|
|
1357
|
-
const agent = implementation as AgentImplementation<TState, unknown>;
|
|
1358
|
-
return {
|
|
1359
|
-
id: node.id,
|
|
1360
|
-
kind: "agent",
|
|
1361
|
-
persist,
|
|
1362
|
-
agent: {
|
|
1363
|
-
capabilities: agent.granted.map((item) => ({
|
|
1364
|
-
name: item.name,
|
|
1365
|
-
requestSchema: JSON.parse(
|
|
1366
|
-
(item as CapabilityImplementation<TState, unknown>).requestJsonSchema,
|
|
1367
|
-
) as unknown,
|
|
1368
|
-
})),
|
|
1369
|
-
...(!agent.output || "raw" in agent.output
|
|
1370
|
-
? {}
|
|
1371
|
-
: { outputSchema: z.toJSONSchema(agent.output.schema, { io: "input" }) }),
|
|
1372
|
-
workspace: agent.workspace !== undefined,
|
|
1373
|
-
},
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
if (node.kind === "parallel") {
|
|
1377
|
-
const parallel = implementation as ParallelImplementation<TState, ParallelBranches<TState>>;
|
|
1378
|
-
return {
|
|
1379
|
-
id: node.id,
|
|
1380
|
-
kind: "parallel",
|
|
1381
|
-
persist,
|
|
1382
|
-
...(parallel.max !== undefined ? { max: parallel.max } : {}),
|
|
1383
|
-
branches: Object.entries(parallel.branches as ParallelBranches<TState>).map(
|
|
1384
|
-
([id, participant]) => ({
|
|
1385
|
-
id,
|
|
1386
|
-
participant: inspectNode(participant),
|
|
1387
|
-
}),
|
|
1388
|
-
),
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
if (node.kind === "terminal") {
|
|
1392
|
-
const terminal = implementation as TerminalImplementation<TState>;
|
|
1393
|
-
return { id: node.id, kind: terminal.failed ? "failure" : "completion", persist };
|
|
1394
|
-
}
|
|
1395
|
-
return { id: node.id, kind: "stage", persist };
|
|
1396
|
-
};
|
|
1397
|
-
return {
|
|
1398
|
-
name: graph.name,
|
|
1399
|
-
stateSchema: z.toJSONSchema(graph.state, { io: "input" }),
|
|
1400
|
-
start: graph.start.id,
|
|
1401
|
-
persist: graph.persist,
|
|
1402
|
-
nodes: graph.nodes.map(inspectNode),
|
|
1403
|
-
routes: graph.routes.map((item, order) => ({
|
|
1404
|
-
id: `route:${order}`,
|
|
1405
|
-
source: item.from.id,
|
|
1406
|
-
target: item.to.id,
|
|
1407
|
-
label: item.label,
|
|
1408
|
-
order,
|
|
1409
|
-
...(item.outcome === undefined ? {} : { outcome: item.outcome }),
|
|
1410
|
-
conditional: item.when !== undefined,
|
|
1411
|
-
})),
|
|
1412
|
-
outputs: graph.outputs.map((item) => item.id),
|
|
1413
|
-
};
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
export interface RunResult<TState> {
|
|
1417
|
-
readonly runId: string;
|
|
1418
|
-
readonly succeeded: boolean;
|
|
1419
|
-
readonly state: TState;
|
|
1420
|
-
readonly summary: string | null;
|
|
1421
|
-
}
|
|
1422
|
-
export type RunObservation =
|
|
1423
|
-
| { readonly version: 1; readonly kind: "stepStarted"; readonly stepId: string }
|
|
1424
|
-
| { readonly version: 1; readonly kind: "stepCompleted"; readonly stepId: string }
|
|
1425
|
-
| { readonly version: 1; readonly kind: "stepCancelled"; readonly stepId: string }
|
|
1426
|
-
| {
|
|
1427
|
-
readonly version: 1;
|
|
1428
|
-
readonly kind: "stepFaulted";
|
|
1429
|
-
readonly stepId: string;
|
|
1430
|
-
readonly error: string;
|
|
1431
|
-
}
|
|
1432
|
-
| {
|
|
1433
|
-
readonly version: 1;
|
|
1434
|
-
readonly kind: "agentText";
|
|
1435
|
-
readonly stepId: string;
|
|
1436
|
-
readonly text: string;
|
|
1437
|
-
}
|
|
1438
|
-
| {
|
|
1439
|
-
readonly version: 1;
|
|
1440
|
-
readonly kind: "agentReasoning";
|
|
1441
|
-
readonly stepId: string;
|
|
1442
|
-
readonly text: string;
|
|
1443
|
-
}
|
|
1444
|
-
| {
|
|
1445
|
-
readonly version: 1;
|
|
1446
|
-
readonly kind: "agentModelSelected";
|
|
1447
|
-
readonly stepId: string;
|
|
1448
|
-
readonly modelId: string;
|
|
1449
|
-
}
|
|
1450
|
-
| {
|
|
1451
|
-
readonly version: 1;
|
|
1452
|
-
readonly kind: "agentUsage";
|
|
1453
|
-
readonly stepId: string;
|
|
1454
|
-
readonly inputTokens: number;
|
|
1455
|
-
readonly outputTokens: number;
|
|
1456
|
-
readonly reasoningTokens: number;
|
|
1457
|
-
readonly currentContextTokens: number;
|
|
1458
|
-
readonly contextWindowTokens: number | null;
|
|
1459
|
-
}
|
|
1460
|
-
| {
|
|
1461
|
-
readonly version: 1;
|
|
1462
|
-
readonly kind: "structuredOutputRejected";
|
|
1463
|
-
readonly stepId: string;
|
|
1464
|
-
readonly attempt: number;
|
|
1465
|
-
readonly problems: readonly { readonly field: string; readonly message: string }[];
|
|
1466
|
-
readonly rawResponse: string;
|
|
1467
|
-
};
|
|
1468
|
-
export interface TerminalPresentationOptions {
|
|
1469
|
-
readonly truncatedToolNames?: readonly string[];
|
|
1470
|
-
}
|
|
1471
|
-
export interface RunOptions {
|
|
1472
|
-
readonly ledgerPath?: string;
|
|
1473
|
-
/** Expose ledger read/search tools to agents. Disabled by default; does not affect logging. */
|
|
1474
|
-
readonly enableLedgerTools?: boolean;
|
|
1475
|
-
readonly signal?: AbortSignal;
|
|
1476
|
-
readonly interactions?: InteractionHandlers;
|
|
1477
|
-
readonly presentation?: "terminal";
|
|
1478
|
-
readonly terminal?: TerminalPresentationOptions;
|
|
1479
|
-
readonly observe?: (
|
|
1480
|
-
event: RunObservation,
|
|
1481
|
-
context: { readonly signal: AbortSignal },
|
|
1482
|
-
) => void | Promise<void>;
|
|
1483
|
-
}
|
|
1484
|
-
const runObservationSchema = z.discriminatedUnion("kind", [
|
|
1485
|
-
z
|
|
1486
|
-
.object({ version: z.literal(1), kind: z.literal("stepStarted"), stepId: z.string().min(1) })
|
|
1487
|
-
.strict(),
|
|
1488
|
-
z
|
|
1489
|
-
.object({ version: z.literal(1), kind: z.literal("stepCompleted"), stepId: z.string().min(1) })
|
|
1490
|
-
.strict(),
|
|
1491
|
-
z
|
|
1492
|
-
.object({ version: z.literal(1), kind: z.literal("stepCancelled"), stepId: z.string().min(1) })
|
|
1493
|
-
.strict(),
|
|
1494
|
-
z
|
|
1495
|
-
.object({
|
|
1496
|
-
version: z.literal(1),
|
|
1497
|
-
kind: z.literal("stepFaulted"),
|
|
1498
|
-
stepId: z.string().min(1),
|
|
1499
|
-
error: z.string(),
|
|
1500
|
-
})
|
|
1501
|
-
.strict(),
|
|
1502
|
-
z
|
|
1503
|
-
.object({
|
|
1504
|
-
version: z.literal(1),
|
|
1505
|
-
kind: z.literal("agentText"),
|
|
1506
|
-
stepId: z.string().min(1),
|
|
1507
|
-
text: z.string(),
|
|
1508
|
-
})
|
|
1509
|
-
.strict(),
|
|
1510
|
-
z
|
|
1511
|
-
.object({
|
|
1512
|
-
version: z.literal(1),
|
|
1513
|
-
kind: z.literal("agentModelSelected"),
|
|
1514
|
-
stepId: z.string().min(1),
|
|
1515
|
-
modelId: z.string().min(1),
|
|
1516
|
-
})
|
|
1517
|
-
.strict(),
|
|
1518
|
-
z
|
|
1519
|
-
.object({
|
|
1520
|
-
version: z.literal(1),
|
|
1521
|
-
kind: z.literal("agentReasoning"),
|
|
1522
|
-
stepId: z.string().min(1),
|
|
1523
|
-
text: z.string(),
|
|
1524
|
-
})
|
|
1525
|
-
.strict(),
|
|
1526
|
-
z
|
|
1527
|
-
.object({
|
|
1528
|
-
version: z.literal(1),
|
|
1529
|
-
kind: z.literal("agentUsage"),
|
|
1530
|
-
stepId: z.string().min(1),
|
|
1531
|
-
inputTokens: z.number().int().nonnegative(),
|
|
1532
|
-
outputTokens: z.number().int().nonnegative(),
|
|
1533
|
-
reasoningTokens: z.number().int().nonnegative(),
|
|
1534
|
-
currentContextTokens: z.number().int().nonnegative(),
|
|
1535
|
-
contextWindowTokens: z.number().int().nonnegative().nullable(),
|
|
1536
|
-
})
|
|
1537
|
-
.strict(),
|
|
1538
|
-
z
|
|
1539
|
-
.object({
|
|
1540
|
-
version: z.literal(1),
|
|
1541
|
-
kind: z.literal("structuredOutputRejected"),
|
|
1542
|
-
stepId: z.string().min(1),
|
|
1543
|
-
attempt: z.number().int().positive(),
|
|
1544
|
-
problems: z.array(z.object({ field: z.string(), message: z.string().min(1) }).strict()),
|
|
1545
|
-
rawResponse: z.string(),
|
|
1546
|
-
})
|
|
1547
|
-
.strict(),
|
|
1548
|
-
]);
|
|
1549
|
-
const acceptedKinds = [
|
|
1550
|
-
"StructuredOutputAccepted",
|
|
1551
|
-
"CapabilityAccepted",
|
|
1552
|
-
"InteractionRequested",
|
|
1553
|
-
"InteractionAnswered",
|
|
1554
|
-
"StepCompleted",
|
|
1555
|
-
] as const;
|
|
1556
|
-
type AcceptedKind = (typeof acceptedKinds)[number];
|
|
1557
|
-
export type AcceptedValue = {
|
|
1558
|
-
[K in AcceptedKind]: {
|
|
1559
|
-
readonly version: 1;
|
|
1560
|
-
readonly kind: K;
|
|
1561
|
-
readonly stepId: string;
|
|
1562
|
-
readonly valueType: string | null;
|
|
1563
|
-
readonly payload: unknown | null;
|
|
1564
|
-
};
|
|
1565
|
-
}[AcceptedKind];
|
|
1566
|
-
const acceptedValueSchema = z
|
|
1567
|
-
.object({
|
|
1568
|
-
kind: z.enum(acceptedKinds),
|
|
1569
|
-
stepId: z.string().min(1),
|
|
1570
|
-
valueType: z.string().min(1).nullable(),
|
|
1571
|
-
payload: z.unknown().nullable(),
|
|
1572
|
-
})
|
|
1573
|
-
.strict()
|
|
1574
|
-
.refine((value) => value.valueType !== null || value.payload !== null, {
|
|
1575
|
-
message: "valueType and payload cannot both be null",
|
|
1576
|
-
});
|
|
1577
|
-
const acceptedValuesSchema = z.array(acceptedValueSchema);
|
|
1578
|
-
const runResultSchema = z
|
|
1579
|
-
.object({
|
|
1580
|
-
runId: z.uuid(),
|
|
1581
|
-
succeeded: z.boolean(),
|
|
1582
|
-
state: z.unknown(),
|
|
1583
|
-
summary: z.string().nullable(),
|
|
1584
|
-
})
|
|
1585
|
-
.strict();
|
|
1586
|
-
|
|
1587
|
-
export async function inspectAccepted(options: {
|
|
1588
|
-
ledgerPath: string;
|
|
1589
|
-
runId: string;
|
|
1590
|
-
}): Promise<readonly AcceptedValue[]> {
|
|
1591
|
-
try {
|
|
1592
|
-
const { inspectAcceptedAsync } = await import("./runtime/loader.mjs");
|
|
1593
|
-
return parseJson(
|
|
1594
|
-
acceptedValuesSchema,
|
|
1595
|
-
await inspectAcceptedAsync(options.ledgerPath, options.runId),
|
|
1596
|
-
"accepted values",
|
|
1597
|
-
).map(
|
|
1598
|
-
(value) =>
|
|
1599
|
-
({
|
|
1600
|
-
version: 1 as const,
|
|
1601
|
-
kind: value.kind,
|
|
1602
|
-
stepId: value.stepId,
|
|
1603
|
-
valueType: value.valueType,
|
|
1604
|
-
payload: value.payload,
|
|
1605
|
-
}) as AcceptedValue,
|
|
1606
|
-
);
|
|
1607
|
-
} catch (error) {
|
|
1608
|
-
if (error instanceof TandemError) {
|
|
1609
|
-
throw error;
|
|
1610
|
-
}
|
|
1611
|
-
throw new TandemRuntimeError("inspect", error);
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
export async function run<TState>(
|
|
1616
|
-
graph: Pipeline<TState>,
|
|
1617
|
-
initial: unknown,
|
|
1618
|
-
options: RunOptions = {},
|
|
1619
|
-
): Promise<RunResult<TState>> {
|
|
1620
|
-
const initialState = serializeBoundary(graph.state, initial, "initial state");
|
|
1621
|
-
if ((graph.persist || graph.nodes.some(participantPersists)) && !options.ledgerPath) {
|
|
1622
|
-
throw new TandemError("ledgerPath is required when persistence is enabled.");
|
|
1623
|
-
}
|
|
1624
|
-
const callbacks = new CallbackRegistry();
|
|
1625
|
-
try {
|
|
1626
|
-
const nodes = graph.nodes.map((node) => compileNode(node, graph.state, callbacks));
|
|
1627
|
-
const routes = graph.routes.map((item) => {
|
|
1628
|
-
const callback = item.when
|
|
1629
|
-
? callbacks.registerSync((state) =>
|
|
1630
|
-
String(item.when!(parseJson(graph.state, state, `route '${item.label}' state`))),
|
|
1631
|
-
)
|
|
1632
|
-
: undefined;
|
|
1633
|
-
return {
|
|
1634
|
-
source: item.from.id,
|
|
1635
|
-
target: item.to.id,
|
|
1636
|
-
label: item.label,
|
|
1637
|
-
outcome: item.outcome,
|
|
1638
|
-
predicateCallback: callback,
|
|
1639
|
-
};
|
|
1640
|
-
});
|
|
1641
|
-
const handlerEntries = interactionHandlerEntries(options.interactions);
|
|
1642
|
-
const members = new Set(graph.nodes);
|
|
1643
|
-
const interactionHandlers = handlerEntries.map((entry, index) => {
|
|
1644
|
-
if (!members.has(entry.interaction as Node<TState>)) {
|
|
1645
|
-
throw new TandemError(
|
|
1646
|
-
`Interaction handler '${entry.interaction.id}' must target a participant in pipeline '${graph.name}'.`,
|
|
1647
|
-
);
|
|
1648
|
-
}
|
|
1649
|
-
const implementation = entry.interaction as InteractionImplementation<
|
|
1650
|
-
TState,
|
|
1651
|
-
unknown,
|
|
1652
|
-
unknown
|
|
1653
|
-
>;
|
|
1654
|
-
const handleCallback = callbacks.registerAsync(async (_, input, signal) => {
|
|
1655
|
-
const response = await entry.handle(
|
|
1656
|
-
parseJson(implementation.requestSchema, input, `${implementation.id} request input`),
|
|
1657
|
-
{ signal },
|
|
1658
|
-
);
|
|
1659
|
-
signal.throwIfAborted();
|
|
1660
|
-
return serializeBoundary(
|
|
1661
|
-
implementation.responseSchema,
|
|
1662
|
-
response,
|
|
1663
|
-
`${implementation.id} response`,
|
|
1664
|
-
);
|
|
1665
|
-
});
|
|
1666
|
-
return { id: `h${index}`, target: entry.interaction.id, handleCallback };
|
|
1667
|
-
});
|
|
1668
|
-
const observationCallback = options.observe
|
|
1669
|
-
? callbacks.registerAsync(async (_, input, signal) => {
|
|
1670
|
-
const event = parseJson(runObservationSchema, input, "run observation");
|
|
1671
|
-
const observationSignal =
|
|
1672
|
-
options.signal?.aborted === true
|
|
1673
|
-
? AbortSignal.abort(options.signal.reason)
|
|
1674
|
-
: event.kind === "stepCancelled" && !signal.aborted
|
|
1675
|
-
? AbortSignal.abort()
|
|
1676
|
-
: signal;
|
|
1677
|
-
await options.observe!(event, { signal: observationSignal });
|
|
1678
|
-
return "";
|
|
1679
|
-
})
|
|
1680
|
-
: undefined;
|
|
1681
|
-
const { runRegisteredGraphAsync } = await import("./runtime/loader.mjs");
|
|
1682
|
-
const resultJson = await runRegisteredGraphAsync(
|
|
1683
|
-
JSON.stringify({
|
|
1684
|
-
contractVersion: 10,
|
|
1685
|
-
name: graph.name,
|
|
1686
|
-
start: graph.start.id,
|
|
1687
|
-
initialState,
|
|
1688
|
-
persist: graph.persist,
|
|
1689
|
-
ledgerPath: options.ledgerPath,
|
|
1690
|
-
enableLedgerTools: options.enableLedgerTools ?? false,
|
|
1691
|
-
presentation: options.presentation,
|
|
1692
|
-
terminal: options.terminal,
|
|
1693
|
-
observationCallback,
|
|
1694
|
-
nodes,
|
|
1695
|
-
routes,
|
|
1696
|
-
outputs: graph.outputs.map((item) => item.id),
|
|
1697
|
-
interactionHandlers,
|
|
1698
|
-
}),
|
|
1699
|
-
(id: string, state: string, input: string) => callbacks.invokeSync(id, state, input),
|
|
1700
|
-
(id: string, state: string, input: string, signal: AbortSignal) =>
|
|
1701
|
-
callbacks.invokeAsync(id, state, input, signal),
|
|
1702
|
-
options.signal,
|
|
1703
|
-
);
|
|
1704
|
-
const result = parseJson(runResultSchema, resultJson, "run result");
|
|
1705
|
-
return { ...result, state: parse(graph.state, result.state, "final state") };
|
|
1706
|
-
} catch (error) {
|
|
1707
|
-
if (error instanceof TandemError) {
|
|
1708
|
-
throw error;
|
|
1709
|
-
}
|
|
1710
|
-
const callbackFailure = callbackContractFailure(error);
|
|
1711
|
-
if (callbackFailure) {
|
|
1712
|
-
throw new ContractValidationError(callbackFailure.boundary, callbackFailure.problems);
|
|
1713
|
-
}
|
|
1714
|
-
if (isCancellationError(error, options.signal?.aborted === true)) {
|
|
1715
|
-
throw new TandemCancellationError(error);
|
|
1716
|
-
}
|
|
1717
|
-
throw new TandemRuntimeError("run", error);
|
|
1718
|
-
} finally {
|
|
1719
|
-
callbacks.dispose();
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
function participantPersists<TState>(node: Node<TState>): boolean {
|
|
1724
|
-
const implementation = node as NodeImplementation<TState>;
|
|
1725
|
-
if (implementation.persist) {
|
|
1726
|
-
return true;
|
|
1727
|
-
}
|
|
1728
|
-
return implementation instanceof ParallelImplementation
|
|
1729
|
-
? Object.values(implementation.branches).some(
|
|
1730
|
-
(branch) => (branch as NodeImplementation<TState>).persist === true,
|
|
1731
|
-
)
|
|
1732
|
-
: false;
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
function issues<T>(schema: z.ZodType<T>, input: string): string {
|
|
1736
|
-
let value: unknown;
|
|
1737
|
-
try {
|
|
1738
|
-
value = JSON.parse(input);
|
|
1739
|
-
} catch {
|
|
1740
|
-
return JSON.stringify([{ path: "$", message: "Invalid JSON" }]);
|
|
1741
|
-
}
|
|
1742
|
-
return issuesParsed(schema, value);
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
function issuesParsed<T>(schema: z.ZodType<T>, value: unknown): string {
|
|
1746
|
-
try {
|
|
1747
|
-
parse(schema, value, "agent contract");
|
|
1748
|
-
return "";
|
|
1749
|
-
} catch (error) {
|
|
1750
|
-
if (error instanceof ContractValidationError) {
|
|
1751
|
-
return JSON.stringify(error.problems);
|
|
1752
|
-
}
|
|
1753
|
-
throw error;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
const validationProblemsSchema = z.array(
|
|
1757
|
-
z.object({ path: z.string(), message: z.string() }).strict(),
|
|
1758
|
-
);
|
|
1759
|
-
function validationProblems(problems: readonly ValidationProblem[], boundary: string): string {
|
|
1760
|
-
return JSON.stringify(parse(validationProblemsSchema, problems, boundary));
|
|
1761
|
-
}
|
|
1762
|
-
function requireInstructions(instructions: string, boundary: string): void {
|
|
1763
|
-
if (typeof instructions !== "string" || instructions.trim().length === 0) {
|
|
1764
|
-
throw new ContractValidationError(boundary, [
|
|
1765
|
-
{ path: "$", message: "Instructions must be a non-blank string." },
|
|
1766
|
-
]);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
function interactionHandlerEntries(
|
|
1770
|
-
handlers: InteractionHandlers | undefined,
|
|
1771
|
-
): readonly RegisteredInteractionHandler[] {
|
|
1772
|
-
if (!handlers) {
|
|
1773
|
-
return [];
|
|
1774
|
-
}
|
|
1775
|
-
if (!(handlers instanceof InteractionHandlersImplementation)) {
|
|
1776
|
-
throw new TandemError("interactions must be created by interactions().");
|
|
1777
|
-
}
|
|
1778
|
-
return handlers.entries;
|
|
1779
|
-
}
|
|
1780
|
-
function compileNode<TState>(
|
|
1781
|
-
node: Node<TState>,
|
|
1782
|
-
stateSchema: z.ZodType<TState>,
|
|
1783
|
-
callbacks: CallbackRegistry,
|
|
1784
|
-
): object {
|
|
1785
|
-
const implementation = node as NodeImplementation<TState>;
|
|
1786
|
-
const base = { id: node.id, persist: implementation.persist };
|
|
1787
|
-
if (implementation instanceof StageImplementation) {
|
|
1788
|
-
const run = callbacks.registerAsync(async (state, _, signal) =>
|
|
1789
|
-
serializeBoundary(
|
|
1790
|
-
stateSchema,
|
|
1791
|
-
await implementation.execute(parseJson(stateSchema, state, `${node.id} input`), {
|
|
1792
|
-
signal,
|
|
1793
|
-
}),
|
|
1794
|
-
`${node.id} output`,
|
|
1795
|
-
),
|
|
1796
|
-
);
|
|
1797
|
-
return { ...base, kind: "stage", runCallback: run };
|
|
1798
|
-
}
|
|
1799
|
-
if (implementation instanceof InteractionImplementation) {
|
|
1800
|
-
const request = callbacks.registerSync((state) =>
|
|
1801
|
-
serializeBoundary(
|
|
1802
|
-
implementation.requestSchema,
|
|
1803
|
-
implementation.request(parseJson(stateSchema, state, `${node.id} state`)),
|
|
1804
|
-
`${node.id} request`,
|
|
1805
|
-
),
|
|
1806
|
-
);
|
|
1807
|
-
const apply = callbacks.registerSync((state, input) =>
|
|
1808
|
-
serializeBoundary(
|
|
1809
|
-
stateSchema,
|
|
1810
|
-
implementation.apply(
|
|
1811
|
-
parseJson(stateSchema, state, `${node.id} state`),
|
|
1812
|
-
parseJson(implementation.responseSchema, input, `${node.id} response input`),
|
|
1813
|
-
),
|
|
1814
|
-
`${node.id} applied state`,
|
|
1815
|
-
),
|
|
1816
|
-
);
|
|
1817
|
-
return {
|
|
1818
|
-
...base,
|
|
1819
|
-
kind: "interaction",
|
|
1820
|
-
requestCallback: request,
|
|
1821
|
-
applyCallback: apply,
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
if (implementation instanceof AgentImplementation) {
|
|
1825
|
-
const message = callbacks.registerSync((state) =>
|
|
1826
|
-
implementation.message(parseJson(stateSchema, state, `${node.id} message state`)),
|
|
1827
|
-
);
|
|
1828
|
-
const output = implementation.output
|
|
1829
|
-
? compileAgentOutput(node.id, implementation.output, stateSchema, callbacks)
|
|
1830
|
-
: undefined;
|
|
1831
|
-
const capabilities = implementation.granted.map((item) =>
|
|
1832
|
-
item[compileCapabilityBrand]({ id: node.id, stateSchema, callbacks }),
|
|
1833
|
-
);
|
|
1834
|
-
const workspace = implementation.workspace
|
|
1835
|
-
? compileWorkspace(node.id, implementation.workspace, stateSchema, callbacks)
|
|
1836
|
-
: undefined;
|
|
1837
|
-
return {
|
|
1838
|
-
...base,
|
|
1839
|
-
kind: "agent",
|
|
1840
|
-
instructions: implementation.instructions,
|
|
1841
|
-
client: { ...implementation.client, verifyModel: implementation.client.verifyModel ?? false },
|
|
1842
|
-
messageCallback: message,
|
|
1843
|
-
output,
|
|
1844
|
-
capabilities,
|
|
1845
|
-
skillDirectories: implementation.skills.map((item) => item.directory),
|
|
1846
|
-
temperature: implementation.temperature,
|
|
1847
|
-
maxOutputTokens: implementation.maxOutputTokens,
|
|
1848
|
-
reasoning: implementation.reasoning,
|
|
1849
|
-
continueSession: implementation.continueSession,
|
|
1850
|
-
checkpoint: implementation.checkpoint
|
|
1851
|
-
? {
|
|
1852
|
-
contextWindowTokens: implementation.checkpoint.contextWindowTokens,
|
|
1853
|
-
maxOutputTokens: implementation.checkpoint.maxOutputTokens,
|
|
1854
|
-
checkpointAtPercent: implementation.checkpoint.checkpointAtPercent,
|
|
1855
|
-
capabilityName: implementation.checkpoint.capability.name,
|
|
1856
|
-
instructions: implementation.checkpoint.instructions,
|
|
1857
|
-
messageCallback: callbacks.registerSync((state, input) =>
|
|
1858
|
-
implementation.checkpoint!.message(
|
|
1859
|
-
parseJson(stateSchema, state, `${node.id} checkpoint state`),
|
|
1860
|
-
Number(input),
|
|
1861
|
-
),
|
|
1862
|
-
),
|
|
1863
|
-
resetSession: (implementation.checkpoint.session ?? "reset") === "reset",
|
|
1864
|
-
disableCompaction: implementation.checkpoint.disableCompaction ?? false,
|
|
1865
|
-
}
|
|
1866
|
-
: undefined,
|
|
1867
|
-
timeoutMilliseconds: implementation.timeoutMs,
|
|
1868
|
-
workspace,
|
|
1869
|
-
};
|
|
1870
|
-
}
|
|
1871
|
-
if (implementation instanceof ParallelImplementation) {
|
|
1872
|
-
const entries = Object.entries(implementation.branches) as [
|
|
1873
|
-
string,
|
|
1874
|
-
Stage<TState> | Agent<TState>,
|
|
1875
|
-
][];
|
|
1876
|
-
const branchIds = entries.map(([branchId]) => branchId);
|
|
1877
|
-
const mergeCallback = callbacks.registerSync((state, input) => {
|
|
1878
|
-
const baseline = parseJson(stateSchema, state, `${node.id} merge baseline`);
|
|
1879
|
-
let raw: unknown;
|
|
1880
|
-
try {
|
|
1881
|
-
raw = JSON.parse(input);
|
|
1882
|
-
} catch {
|
|
1883
|
-
throw new ContractValidationError(`${node.id} merge branches`, [
|
|
1884
|
-
{ path: "$", message: "Invalid JSON" },
|
|
1885
|
-
]);
|
|
1886
|
-
}
|
|
1887
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1888
|
-
throw new ContractValidationError(`${node.id} merge branches`, [
|
|
1889
|
-
{ path: "$", message: "Expected a branch-state object." },
|
|
1890
|
-
]);
|
|
1891
|
-
}
|
|
1892
|
-
const values = raw as Record<string, unknown>;
|
|
1893
|
-
if (!isDeepStrictEqual(Object.keys(values).sort(), [...branchIds].sort())) {
|
|
1894
|
-
throw new ContractValidationError(`${node.id} merge branches`, [
|
|
1895
|
-
{ path: "$", message: "Branch-state keys do not match the authored branches." },
|
|
1896
|
-
]);
|
|
1897
|
-
}
|
|
1898
|
-
const parsed = Object.fromEntries(
|
|
1899
|
-
branchIds.map((branchId) => [
|
|
1900
|
-
branchId,
|
|
1901
|
-
parse(stateSchema, values[branchId], `${node.id} branch '${branchId}' state`),
|
|
1902
|
-
]),
|
|
1903
|
-
) as { readonly [key: string]: TState };
|
|
1904
|
-
return serializeBoundary(
|
|
1905
|
-
stateSchema,
|
|
1906
|
-
implementation.merge(baseline, parsed),
|
|
1907
|
-
`${node.id} merged state`,
|
|
1908
|
-
);
|
|
1909
|
-
});
|
|
1910
|
-
return {
|
|
1911
|
-
...base,
|
|
1912
|
-
kind: "parallel",
|
|
1913
|
-
...(implementation.max !== undefined ? { max: implementation.max } : {}),
|
|
1914
|
-
branches: entries.map(([id, participant]) => ({
|
|
1915
|
-
id,
|
|
1916
|
-
participant: compileNode(participant, stateSchema, callbacks),
|
|
1917
|
-
})),
|
|
1918
|
-
mergeCallback,
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
|
-
const terminal = implementation as TerminalImplementation<TState>;
|
|
1922
|
-
const summary = callbacks.registerSync((state) =>
|
|
1923
|
-
terminal.summary(parseJson(stateSchema, state, `${node.id} state`)),
|
|
1924
|
-
);
|
|
1925
|
-
return { ...base, kind: terminal.failed ? "failure" : "completion", summaryCallback: summary };
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
function compileWorkspace<TState>(
|
|
1929
|
-
id: string,
|
|
1930
|
-
configuration: AgentWorkspaceConfiguration<TState>,
|
|
1931
|
-
stateSchema: z.ZodType<TState>,
|
|
1932
|
-
callbacks: CallbackRegistry,
|
|
1933
|
-
): object {
|
|
1934
|
-
if (!(configuration instanceof AgentWorkspaceConfigurationImplementation)) {
|
|
1935
|
-
throw new TandemError(
|
|
1936
|
-
`Agent '${id}' workspace must be created by agentWorkspace().withTools().`,
|
|
1937
|
-
);
|
|
1938
|
-
}
|
|
1939
|
-
const workspace = configuration.workspace;
|
|
1940
|
-
const pathCallback = callbacks.registerSync((state) => {
|
|
1941
|
-
const value = workspace.path(parseJson(stateSchema, state, `${id} workspace path state`));
|
|
1942
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1943
|
-
throw new TandemError(`Agent '${id}' workspace path must be non-blank.`);
|
|
1944
|
-
}
|
|
1945
|
-
return value;
|
|
1946
|
-
});
|
|
1947
|
-
const commandsCallback = callbacks.registerSync((state) => {
|
|
1948
|
-
const typedState = parseJson(stateSchema, state, `${id} workspace commands state`);
|
|
1949
|
-
const value =
|
|
1950
|
-
typeof workspace.commandSource === "function"
|
|
1951
|
-
? workspace.commandSource(typedState)
|
|
1952
|
-
: (workspace.commandSource ?? []);
|
|
1953
|
-
validateAgentCommands(value, `Agent '${id}' workspace commands`);
|
|
1954
|
-
return serializeBoundary(
|
|
1955
|
-
z.array(
|
|
1956
|
-
z
|
|
1957
|
-
.object({
|
|
1958
|
-
name: z.string(),
|
|
1959
|
-
description: z.string(),
|
|
1960
|
-
command: z.string(),
|
|
1961
|
-
arguments: z.array(z.string()).optional(),
|
|
1962
|
-
})
|
|
1963
|
-
.strict(),
|
|
1964
|
-
),
|
|
1965
|
-
value,
|
|
1966
|
-
`${id} workspace commands`,
|
|
1967
|
-
);
|
|
1968
|
-
});
|
|
1969
|
-
const selected = new Set<AgentToolSelection>();
|
|
1970
|
-
const toolGroups = configuration.groups.map((group, index) => {
|
|
1971
|
-
let includeCommands = false;
|
|
1972
|
-
const tools: AgentToolName[] = [];
|
|
1973
|
-
for (const tool of group.tools) {
|
|
1974
|
-
if (typeof tool === "string") {
|
|
1975
|
-
if (selected.has(tool)) {
|
|
1976
|
-
throw new TandemError(`Agent '${id}' selects '${tool}' more than once.`);
|
|
1977
|
-
}
|
|
1978
|
-
selected.add(tool);
|
|
1979
|
-
tools.push(tool);
|
|
1980
|
-
} else {
|
|
1981
|
-
if (tool[commandSelectionBrand] !== workspace) {
|
|
1982
|
-
throw new TandemError(`Agent '${id}' selects commands from another workspace.`);
|
|
1983
|
-
}
|
|
1984
|
-
if (workspace.commandSource === undefined) {
|
|
1985
|
-
throw new TandemError(
|
|
1986
|
-
`Agent '${id}' selects workspace commands without declaring a command catalogue.`,
|
|
1987
|
-
);
|
|
1988
|
-
}
|
|
1989
|
-
if (selected.has(tool)) {
|
|
1990
|
-
throw new TandemError(`Agent '${id}' selects workspace commands twice.`);
|
|
1991
|
-
}
|
|
1992
|
-
selected.add(tool);
|
|
1993
|
-
includeCommands = true;
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
const whenCallback = group.predicate
|
|
1997
|
-
? callbacks.registerSync((state) => {
|
|
1998
|
-
const value = group.predicate!(
|
|
1999
|
-
parseJson(stateSchema, state, `${id} tool group ${index} state`),
|
|
2000
|
-
);
|
|
2001
|
-
if (typeof value !== "boolean") {
|
|
2002
|
-
throw new TandemError(
|
|
2003
|
-
`Agent '${id}' tool group ${index} predicate must return a boolean.`,
|
|
2004
|
-
);
|
|
2005
|
-
}
|
|
2006
|
-
return String(value);
|
|
2007
|
-
})
|
|
2008
|
-
: undefined;
|
|
2009
|
-
return { tools, includeCommands, whenCallback };
|
|
2010
|
-
});
|
|
2011
|
-
const interceptCallback = configuration.interceptTool
|
|
2012
|
-
? callbacks.registerAsync(async (state, input, signal) => {
|
|
2013
|
-
const typedState = parseJson(stateSchema, state, `${id} tool interception state`);
|
|
2014
|
-
let invocation: AgentToolInvocation;
|
|
2015
|
-
try {
|
|
2016
|
-
invocation = JSON.parse(input) as AgentToolInvocation;
|
|
2017
|
-
} catch {
|
|
2018
|
-
throw new TandemError(`Agent '${id}' received an invalid tool interception payload.`);
|
|
2019
|
-
}
|
|
2020
|
-
const result = await configuration.interceptTool!(typedState, invocation, { signal });
|
|
2021
|
-
if (result !== null && typeof result !== "string") {
|
|
2022
|
-
throw new TandemError(`Agent '${id}' tool interceptor must return a string or null.`);
|
|
2023
|
-
}
|
|
2024
|
-
return JSON.stringify(result);
|
|
2025
|
-
})
|
|
2026
|
-
: undefined;
|
|
2027
|
-
return { pathCallback, commandsCallback, toolGroups, interceptCallback };
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
function compileAgentOutput<TState, TOutput>(
|
|
2031
|
-
id: string,
|
|
2032
|
-
output: {
|
|
2033
|
-
instructions: string;
|
|
2034
|
-
schema?: z.ZodType<TOutput>;
|
|
2035
|
-
raw?: true;
|
|
2036
|
-
parse?: (response: string) => TOutput;
|
|
2037
|
-
validateFor?: (state: TState, output: TOutput) => readonly ValidationProblem[];
|
|
2038
|
-
apply: (state: TState, output: TOutput) => TState;
|
|
2039
|
-
},
|
|
2040
|
-
stateSchema: z.ZodType<TState>,
|
|
2041
|
-
callbacks: CallbackRegistry,
|
|
2042
|
-
): object {
|
|
2043
|
-
const raw = output.raw === true;
|
|
2044
|
-
const validate = raw
|
|
2045
|
-
? callbacks.registerSync((_, input) =>
|
|
2046
|
-
issuesParsed(
|
|
2047
|
-
z.unknown(),
|
|
2048
|
-
(output.parse as (response: string) => TOutput)(JSON.parse(input) as string),
|
|
2049
|
-
),
|
|
2050
|
-
)
|
|
2051
|
-
: callbacks.registerSync((_, input) => issues(output.schema!, input));
|
|
2052
|
-
const validateFor = output.validateFor
|
|
2053
|
-
? callbacks.registerSync((state, input) =>
|
|
2054
|
-
validationProblems(
|
|
2055
|
-
output.validateFor!(
|
|
2056
|
-
parseJson(stateSchema, state, `${id} state`),
|
|
2057
|
-
raw
|
|
2058
|
-
? (JSON.parse(input) as TOutput)
|
|
2059
|
-
: parseJson(output.schema!, input, `${id} output`),
|
|
2060
|
-
),
|
|
2061
|
-
`${id} output contextual validation`,
|
|
2062
|
-
),
|
|
2063
|
-
)
|
|
2064
|
-
: undefined;
|
|
2065
|
-
const apply = callbacks.registerSync((state, input) =>
|
|
2066
|
-
serializeBoundary(
|
|
2067
|
-
stateSchema,
|
|
2068
|
-
output.apply(
|
|
2069
|
-
parseJson(stateSchema, state, `${id} state`),
|
|
2070
|
-
raw
|
|
2071
|
-
? (JSON.parse(input) as TOutput)
|
|
2072
|
-
: parseJson(output.schema!, input, `${id} output`),
|
|
2073
|
-
),
|
|
2074
|
-
`${id} applied state`,
|
|
2075
|
-
),
|
|
2076
|
-
);
|
|
2077
|
-
return {
|
|
2078
|
-
instructions: output.instructions,
|
|
2079
|
-
...(raw
|
|
2080
|
-
? {
|
|
2081
|
-
raw: true,
|
|
2082
|
-
rawParseCallback: callbacks.registerSync((_, input) =>
|
|
2083
|
-
JSON.stringify(
|
|
2084
|
-
(output.parse as (response: string) => TOutput)(input),
|
|
2085
|
-
),
|
|
2086
|
-
),
|
|
2087
|
-
}
|
|
2088
|
-
: {
|
|
2089
|
-
jsonSchema: inputJsonSchema(output.schema!, `${id} output schema`),
|
|
2090
|
-
validateCallback: validate,
|
|
2091
|
-
}),
|
|
2092
|
-
validateForCallback: validateFor,
|
|
2093
|
-
applyCallback: apply,
|
|
2094
|
-
valueType: `${id}.output`,
|
|
2095
|
-
};
|
|
2096
|
-
}
|