@unigent/cli 0.1.1 → 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.
Files changed (76) hide show
  1. package/dist/register.js +9 -1
  2. package/dist/register.js.map +1 -1
  3. package/package.json +2 -11
  4. package/node_modules/@unigent/core/dist/adapter_process.d.ts +0 -25
  5. package/node_modules/@unigent/core/dist/adapter_process.d.ts.map +0 -1
  6. package/node_modules/@unigent/core/dist/adapter_process.js +0 -46
  7. package/node_modules/@unigent/core/dist/adapter_process.js.map +0 -1
  8. package/node_modules/@unigent/core/dist/args.d.ts +0 -17
  9. package/node_modules/@unigent/core/dist/args.d.ts.map +0 -1
  10. package/node_modules/@unigent/core/dist/args.js +0 -233
  11. package/node_modules/@unigent/core/dist/args.js.map +0 -1
  12. package/node_modules/@unigent/core/dist/backend.d.ts +0 -83
  13. package/node_modules/@unigent/core/dist/backend.d.ts.map +0 -1
  14. package/node_modules/@unigent/core/dist/backend.js +0 -2
  15. package/node_modules/@unigent/core/dist/backend.js.map +0 -1
  16. package/node_modules/@unigent/core/dist/checkpoint.d.ts +0 -26
  17. package/node_modules/@unigent/core/dist/checkpoint.d.ts.map +0 -1
  18. package/node_modules/@unigent/core/dist/checkpoint.js +0 -107
  19. package/node_modules/@unigent/core/dist/checkpoint.js.map +0 -1
  20. package/node_modules/@unigent/core/dist/checkpoint_fingerprint.d.ts +0 -4
  21. package/node_modules/@unigent/core/dist/checkpoint_fingerprint.d.ts.map +0 -1
  22. package/node_modules/@unigent/core/dist/checkpoint_fingerprint.js +0 -41
  23. package/node_modules/@unigent/core/dist/checkpoint_fingerprint.js.map +0 -1
  24. package/node_modules/@unigent/core/dist/completion.d.ts +0 -9
  25. package/node_modules/@unigent/core/dist/completion.d.ts.map +0 -1
  26. package/node_modules/@unigent/core/dist/completion.js +0 -4
  27. package/node_modules/@unigent/core/dist/completion.js.map +0 -1
  28. package/node_modules/@unigent/core/dist/errors.d.ts +0 -43
  29. package/node_modules/@unigent/core/dist/errors.d.ts.map +0 -1
  30. package/node_modules/@unigent/core/dist/errors.js +0 -48
  31. package/node_modules/@unigent/core/dist/errors.js.map +0 -1
  32. package/node_modules/@unigent/core/dist/events.d.ts +0 -101
  33. package/node_modules/@unigent/core/dist/events.d.ts.map +0 -1
  34. package/node_modules/@unigent/core/dist/events.js +0 -105
  35. package/node_modules/@unigent/core/dist/events.js.map +0 -1
  36. package/node_modules/@unigent/core/dist/index.d.ts +0 -19
  37. package/node_modules/@unigent/core/dist/index.d.ts.map +0 -1
  38. package/node_modules/@unigent/core/dist/index.js +0 -10
  39. package/node_modules/@unigent/core/dist/index.js.map +0 -1
  40. package/node_modules/@unigent/core/dist/json_schema_validator.d.ts +0 -8
  41. package/node_modules/@unigent/core/dist/json_schema_validator.d.ts.map +0 -1
  42. package/node_modules/@unigent/core/dist/json_schema_validator.js +0 -27
  43. package/node_modules/@unigent/core/dist/json_schema_validator.js.map +0 -1
  44. package/node_modules/@unigent/core/dist/mcp.d.ts +0 -12
  45. package/node_modules/@unigent/core/dist/mcp.d.ts.map +0 -1
  46. package/node_modules/@unigent/core/dist/mcp.js +0 -117
  47. package/node_modules/@unigent/core/dist/mcp.js.map +0 -1
  48. package/node_modules/@unigent/core/dist/runtime.d.ts +0 -109
  49. package/node_modules/@unigent/core/dist/runtime.d.ts.map +0 -1
  50. package/node_modules/@unigent/core/dist/runtime.js +0 -976
  51. package/node_modules/@unigent/core/dist/runtime.js.map +0 -1
  52. package/node_modules/@unigent/core/dist/schema.d.ts +0 -15
  53. package/node_modules/@unigent/core/dist/schema.d.ts.map +0 -1
  54. package/node_modules/@unigent/core/dist/schema.js +0 -71
  55. package/node_modules/@unigent/core/dist/schema.js.map +0 -1
  56. package/node_modules/@unigent/core/dist/source_tools.d.ts +0 -7
  57. package/node_modules/@unigent/core/dist/source_tools.d.ts.map +0 -1
  58. package/node_modules/@unigent/core/dist/source_tools.js +0 -450
  59. package/node_modules/@unigent/core/dist/source_tools.js.map +0 -1
  60. package/node_modules/@unigent/core/dist/tool.d.ts +0 -41
  61. package/node_modules/@unigent/core/dist/tool.d.ts.map +0 -1
  62. package/node_modules/@unigent/core/dist/tool.js +0 -43
  63. package/node_modules/@unigent/core/dist/tool.js.map +0 -1
  64. package/node_modules/@unigent/core/dist/tools.d.ts +0 -7
  65. package/node_modules/@unigent/core/dist/tools.d.ts.map +0 -1
  66. package/node_modules/@unigent/core/dist/tools.js +0 -3
  67. package/node_modules/@unigent/core/dist/tools.js.map +0 -1
  68. package/node_modules/@unigent/core/dist/trace.d.ts +0 -82
  69. package/node_modules/@unigent/core/dist/trace.d.ts.map +0 -1
  70. package/node_modules/@unigent/core/dist/trace.js +0 -235
  71. package/node_modules/@unigent/core/dist/trace.js.map +0 -1
  72. package/node_modules/@unigent/core/dist/usage.d.ts +0 -26
  73. package/node_modules/@unigent/core/dist/usage.d.ts.map +0 -1
  74. package/node_modules/@unigent/core/dist/usage.js +0 -47
  75. package/node_modules/@unigent/core/dist/usage.js.map +0 -1
  76. package/node_modules/@unigent/core/package.json +0 -68
@@ -1,976 +0,0 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
- import { randomUUID } from "node:crypto";
3
- import { checkpointFingerprint } from "./checkpoint_fingerprint.js";
4
- import { AgentBackendUnavailableError, AgentBudgetExceededError, AgentCancelledError, AgentConcurrencyError, AgentConfigError, AgentRaisedError, AgentRepairExhaustedError, AgentTimeoutError, } from "./errors.js";
5
- import { EventLog, publishRunControl } from "./events.js";
6
- import { optionalJsonSchema, parseSchema } from "./schema.js";
7
- import { compileSourceTools } from "./source_tools.js";
8
- import { compilePortableTool, } from "./tool.js";
9
- import { emptyUsage, UsageAccount, usageFromBackend } from "./usage.js";
10
- const executionContext = new AsyncLocalStorage();
11
- const DURATION_PATTERN = /^(\d+(?:\.\d+)?)(s|m|h)$/;
12
- const DURATION_MULTIPLIER = { s: 1000, m: 60_000, h: 3_600_000 };
13
- const DEFAULT_REPAIR_ATTEMPTS = 3;
14
- const DEFAULT_NESTED_DEPTH = 8;
15
- const DEFAULT_RETAINED_TRACES = 50;
16
- const CURRENCY_DECIMALS = 4;
17
- const RETURN_TOOL_NAME = "unigent_return";
18
- const FAIL_TOOL_NAME = "unigent_fail";
19
- const RESERVED_TOOL_PREFIX = "unigent_";
20
- const UNIGENT_NOTICE_BEGIN = "<!-- unigent:begin -->";
21
- const UNIGENT_NOTICE_END = "<!-- unigent:end -->";
22
- const checkpointFlights = new WeakMap();
23
- function durationMilliseconds(duration) {
24
- const match = DURATION_PATTERN.exec(duration);
25
- const value = match?.[1];
26
- const unit = match?.[2];
27
- if (value === undefined || unit === undefined) {
28
- throw new AgentConfigError(`invalid duration: ${duration}`);
29
- }
30
- return Number(value) * DURATION_MULTIPLIER[unit];
31
- }
32
- function tighterNumber(wider, narrower) {
33
- if (wider === undefined) {
34
- return narrower;
35
- }
36
- if (narrower === undefined) {
37
- return wider;
38
- }
39
- return Math.min(wider, narrower);
40
- }
41
- function tighterDuration(wider, narrower) {
42
- if (wider === undefined) {
43
- return narrower;
44
- }
45
- if (narrower === undefined) {
46
- return wider;
47
- }
48
- return durationMilliseconds(wider) <= durationMilliseconds(narrower) ? wider : narrower;
49
- }
50
- function mergeLimits(wider, narrower) {
51
- if (narrower === undefined) {
52
- return wider;
53
- }
54
- const budgetUsd = tighterNumber(wider.budgetUsd, narrower.budgetUsd);
55
- const turnDuration = tighterDuration(wider.turnDuration, narrower.turnDuration);
56
- const nestedAgentDepth = tighterNumber(wider.nestedAgentDepth, narrower.nestedAgentDepth);
57
- return {
58
- ...(budgetUsd === undefined ? {} : { budgetUsd }),
59
- ...(turnDuration === undefined ? {} : { turnDuration }),
60
- ...(nestedAgentDepth === undefined ? {} : { nestedAgentDepth }),
61
- };
62
- }
63
- function mergeSystemPrompt(wider, narrower) {
64
- if (narrower === undefined || "replace" in narrower) {
65
- return narrower ?? wider;
66
- }
67
- if (wider === undefined) {
68
- return narrower;
69
- }
70
- if ("replace" in wider) {
71
- return { replace: `${wider.replace}\n${narrower.append}` };
72
- }
73
- return { append: `${wider.append}\n${narrower.append}` };
74
- }
75
- function mergeCheckpoint(configured, override) {
76
- if (override === undefined) {
77
- return configured;
78
- }
79
- return override === false ? undefined : override;
80
- }
81
- function mergeConfig(config, overrides) {
82
- return validateResolvedConfig({
83
- ...config,
84
- backend: overrides.backend ?? config.backend,
85
- model: overrides.model ?? config.model,
86
- thinking: overrides.thinking ?? config.thinking,
87
- systemPrompt: mergeSystemPrompt(config.systemPrompt, overrides.systemPrompt),
88
- retries: overrides.retries ?? config.retries,
89
- repairAttempts: overrides.repairAttempts ?? config.repairAttempts,
90
- limits: mergeLimits(config.limits, overrides.limits),
91
- checkpoint: mergeCheckpoint(config.checkpoint, overrides.checkpoint),
92
- checkpointKey: overrides.checkpointKey ?? config.checkpointKey,
93
- });
94
- }
95
- function validateNonNegativeInteger(name, value) {
96
- if (!Number.isSafeInteger(value) || value < 0) {
97
- throw new AgentConfigError(`${name} must be a non-negative safe integer`);
98
- }
99
- }
100
- function validateResolvedConfig(config) {
101
- validateNonNegativeInteger("retries", config.retries);
102
- validateNonNegativeInteger("repairAttempts", config.repairAttempts);
103
- if (config.limits.nestedAgentDepth !== undefined) {
104
- validateNonNegativeInteger("limits.nestedAgentDepth", config.limits.nestedAgentDepth);
105
- }
106
- if (config.limits.budgetUsd !== undefined &&
107
- (!Number.isFinite(config.limits.budgetUsd) || config.limits.budgetUsd < 0)) {
108
- throw new AgentConfigError("limits.budgetUsd must be a non-negative finite number");
109
- }
110
- if (config.limits.turnDuration !== undefined) {
111
- durationMilliseconds(config.limits.turnDuration);
112
- }
113
- return config;
114
- }
115
- function stringifyToolOutput(output) {
116
- if (typeof output === "string") {
117
- return output;
118
- }
119
- const serialized = JSON.stringify([output]);
120
- return serialized.slice(1, -1);
121
- }
122
- function throwFatalError(state) {
123
- const fatalError = state.fatalError;
124
- if (fatalError !== undefined) {
125
- throw fatalError;
126
- }
127
- }
128
- function recordRepairFailure(state, config, message) {
129
- state.repairFailures += 1;
130
- const context = executionContext.getStore();
131
- context?.eventLog.emit({
132
- type: "repair",
133
- spanId: context.spanId,
134
- attempt: state.repairFailures,
135
- error: message,
136
- });
137
- if (state.repairFailures > config.repairAttempts) {
138
- state.fatalError = new AgentRepairExhaustedError(message);
139
- return { content: message, isError: true, terminate: true };
140
- }
141
- return { content: message, isError: true };
142
- }
143
- async function executeUserTool(tool, input, config, state) {
144
- const parent = executionContext.getStore();
145
- if (parent === undefined) {
146
- throw new AgentConfigError("tool executed outside an active Unigent run");
147
- }
148
- state.completedUserTool = true;
149
- const spanId = randomUUID();
150
- const started = performance.now();
151
- let outcome = "succeeded";
152
- let errorMessage;
153
- parent.eventLog.emit({
154
- type: "span_start",
155
- spanId,
156
- parentSpanId: parent.spanId,
157
- name: tool.name,
158
- kind: "tool",
159
- });
160
- try {
161
- const output = await executionContext.run({ ...parent, spanId }, async () => tool.invoke(input));
162
- state.repairFailures = 0;
163
- return { content: stringifyToolOutput(output), isError: false };
164
- }
165
- catch (error) {
166
- const message = error instanceof Error ? error.message : String(error);
167
- outcome = "failed";
168
- errorMessage = message;
169
- return recordRepairFailure(state, config, message);
170
- }
171
- finally {
172
- parent.eventLog.emit({
173
- type: "span_end",
174
- spanId,
175
- parentSpanId: parent.spanId,
176
- durationMs: performance.now() - started,
177
- usage: emptyUsage(),
178
- outcome,
179
- ...(errorMessage === undefined ? {} : { error: errorMessage }),
180
- });
181
- }
182
- }
183
- function buildTools(config, completion, state) {
184
- const tools = config.compiledTools.map((compiled) => ({
185
- name: compiled.name,
186
- description: compiled.description,
187
- ...(compiled.promptSnippet === undefined ? {} : { promptSnippet: compiled.promptSnippet }),
188
- ...(compiled.promptGuidelines === undefined
189
- ? {}
190
- : { promptGuidelines: compiled.promptGuidelines }),
191
- parameters: compiled.parameters,
192
- execute: async (input) => executeUserTool(compiled, input, config, state),
193
- }));
194
- if (config.hasFailTool) {
195
- tools.push({
196
- name: FAIL_TOOL_NAME,
197
- description: "Terminate the current run with a deliberate typed failure.",
198
- parameters: {
199
- type: "object",
200
- properties: { message: { type: "string" }, code: { type: "string" } },
201
- required: ["message"],
202
- additionalProperties: false,
203
- },
204
- execute: async (input) => {
205
- const record = typeof input === "object" && input !== null ? input : {};
206
- const message = "message" in record ? String(record.message) : "agent raised a failure";
207
- const code = "code" in record ? String(record.code) : undefined;
208
- state.fatalError = new AgentRaisedError(message, code);
209
- return await Promise.resolve({ content: message, isError: true, terminate: true });
210
- },
211
- });
212
- }
213
- if (completion.kind !== "prose") {
214
- const outputSchema = completion.kind === "schema" ? (optionalJsonSchema(completion.schema) ?? {}) : undefined;
215
- tools.push({
216
- name: RETURN_TOOL_NAME,
217
- description: completion.kind === "done"
218
- ? "Signal that all requested side effects are complete and terminate without prose."
219
- : "Return the structured result and terminate the current run.",
220
- parameters: outputSchema === undefined
221
- ? { type: "object", additionalProperties: false }
222
- : {
223
- type: "object",
224
- properties: { value: outputSchema },
225
- required: ["value"],
226
- additionalProperties: false,
227
- },
228
- execute: async (input) => {
229
- if (completion.kind === "done") {
230
- state.output = undefined;
231
- state.hasOutput = true;
232
- return {
233
- content: "Completion accepted. Stop now without writing a prose response.",
234
- isError: false,
235
- terminate: true,
236
- };
237
- }
238
- const value = typeof input === "object" && input !== null && "value" in input ? input.value : undefined;
239
- const parsed = await parseSchema(completion.schema, value);
240
- if (parsed.error !== undefined) {
241
- return recordRepairFailure(state, config, parsed.error);
242
- }
243
- state.output = parsed.value;
244
- state.hasOutput = true;
245
- return { content: "structured result accepted", isError: false, terminate: true };
246
- },
247
- });
248
- }
249
- return tools;
250
- }
251
- function unigentSystemPrompt(config) {
252
- const failure = config.hasFailTool
253
- ? ` If ${FAIL_TOOL_NAME} is available, call it only when you cannot complete the user's instructions. Do not use it for recoverable tool errors or uncertainty.`
254
- : "";
255
- const instruction = `When ${RETURN_TOOL_NAME} is available and you have fully completed the user's instructions, you MUST call it with the requested result or completion signal because the caller reads that tool call rather than your prose and omitting it fails the task; when it is unavailable, respond directly.${failure}`;
256
- const protocol = [UNIGENT_NOTICE_BEGIN, instruction, UNIGENT_NOTICE_END].join("\n");
257
- const authored = config.systemPrompt;
258
- let text = "";
259
- if (authored !== undefined) {
260
- text = "replace" in authored ? authored.replace : authored.append;
261
- }
262
- const snippets = config.compiledTools.flatMap((tool) => tool.promptSnippet === undefined ? [] : [`- ${tool.name}: ${tool.promptSnippet}`]);
263
- const guidelines = config.compiledTools.flatMap((tool) => tool.promptGuidelines?.map((guideline) => `- ${guideline}`) ?? []);
264
- const promptMetadata = [
265
- ...(snippets.length === 0 ? [] : ["Available Unigent tools:", ...snippets]),
266
- ...(guidelines.length === 0 ? [] : ["Unigent tool guidelines:", ...guidelines]),
267
- ].join("\n");
268
- return [protocol, promptMetadata, text].filter((part) => part.length > 0).join("\n\n");
269
- }
270
- function emitBackendEvent(log, spanId, event) {
271
- log.emit({ ...event, spanId });
272
- }
273
- function validateCapabilities(config) {
274
- if (config.limits.budgetUsd !== undefined && !config.backend.capabilities.reportsCost) {
275
- throw new AgentConfigError(`${config.backend.name} cannot enforce budgetUsd because it reports no cost`);
276
- }
277
- }
278
- async function oneBackendTurn(session, request, context, turnDuration) {
279
- if (request.signal.aborted) {
280
- throw new AgentCancelledError("run was cancelled");
281
- }
282
- const timeout = turnDuration === undefined
283
- ? undefined
284
- : AbortSignal.timeout(durationMilliseconds(turnDuration));
285
- const turnSignal = timeout === undefined ? request.signal : AbortSignal.any([request.signal, timeout]);
286
- let rejectAbort;
287
- const aborted = new Promise((_resolve, reject) => {
288
- rejectAbort = reject;
289
- });
290
- const onAbort = () => {
291
- const error = timeout?.aborted === true && !request.signal.aborted
292
- ? new AgentTimeoutError(`turn exceeded ${turnDuration}`)
293
- : new AgentCancelledError("run was cancelled");
294
- rejectAbort?.(error);
295
- };
296
- turnSignal.addEventListener("abort", onAbort, { once: true });
297
- try {
298
- return await Promise.race([
299
- executionContext.run(context, async () => session.runTurn({ ...request, signal: turnSignal })),
300
- aborted,
301
- ]);
302
- }
303
- finally {
304
- turnSignal.removeEventListener("abort", onAbort);
305
- }
306
- }
307
- async function driveRun(config, prompt, completion, sessionFactory, canRetrySession, context) {
308
- validateCapabilities(config);
309
- const currentCost = config.limits.budgetUsd === undefined ? undefined : knownCost(context.scope.usage.snapshot());
310
- if (config.limits.budgetUsd !== undefined &&
311
- currentCost !== undefined &&
312
- currentCost >= config.limits.budgetUsd) {
313
- throw new AgentBudgetExceededError("scope budget is already exhausted");
314
- }
315
- const state = {
316
- repairFailures: 0,
317
- completedUserTool: false,
318
- output: undefined,
319
- hasOutput: false,
320
- fatalError: undefined,
321
- };
322
- const tools = buildTools(config, completion, state);
323
- const systemPrompt = unigentSystemPrompt(config);
324
- const request = (turnPrompt) => ({
325
- systemPrompt,
326
- systemPromptMode: config.systemPrompt !== undefined && "replace" in config.systemPrompt ? "replace" : "append",
327
- prompt: turnPrompt,
328
- tools,
329
- ...(config.thinking === undefined ? {} : { thinking: config.thinking }),
330
- signal: context.signal,
331
- onEvent: (event) => emitBackendEvent(context.eventLog, context.spanId, event),
332
- });
333
- const initial = await runWithRetries(config, prompt, sessionFactory, canRetrySession, context, state, request);
334
- const { result, session } = initial;
335
- context.usage.add(usageFromBackend(result.usage));
336
- throwFatalError(state);
337
- if (completion.kind === "prose") {
338
- return result.text;
339
- }
340
- for (let repair = 0; !state.hasOutput && repair < config.repairAttempts; repair += 1) {
341
- const repaired = await oneBackendTurn(session, request(`You did not call ${RETURN_TOOL_NAME}. Call it now with the required result.`), context, config.limits.turnDuration);
342
- context.usage.add(usageFromBackend(repaired.usage));
343
- throwFatalError(state);
344
- }
345
- if (!state.hasOutput) {
346
- throw new AgentRepairExhaustedError(`agent never called ${RETURN_TOOL_NAME}`);
347
- }
348
- return state.output;
349
- }
350
- function knownCost(usage) {
351
- if (usage.calls === 0) {
352
- return 0;
353
- }
354
- if (usage.costUsd === undefined) {
355
- throw new AgentConfigError("scope cost is unknown because a nested backend reported no cost");
356
- }
357
- return usage.costUsd;
358
- }
359
- function checkpointContext(config, prompt, completion, enabled) {
360
- if (!enabled || config.checkpoint === undefined) {
361
- return;
362
- }
363
- const schema = completion.kind === "schema" ? optionalJsonSchema(completion.schema) : undefined;
364
- if (completion.kind === "schema" && schema === undefined) {
365
- throw new AgentConfigError("checkpointed structured output requires a Standard JSON Schema projection");
366
- }
367
- const tools = config.compiledTools.map((tool) => ({
368
- name: tool.name,
369
- description: tool.description,
370
- promptSnippet: tool.promptSnippet,
371
- promptGuidelines: tool.promptGuidelines,
372
- parameters: tool.parameters,
373
- checkpointKey: tool.checkpointKey,
374
- }));
375
- return {
376
- store: config.checkpoint,
377
- key: checkpointFingerprint({
378
- mode: completion.kind,
379
- schema,
380
- prompt,
381
- systemPrompt: unigentSystemPrompt(config),
382
- systemPromptMode: config.systemPrompt !== undefined && "replace" in config.systemPrompt
383
- ? "replace"
384
- : "append",
385
- backend: config.backend.checkpointKey ?? config.backend.name,
386
- model: config.model,
387
- thinking: config.thinking,
388
- tools,
389
- failTool: config.hasFailTool,
390
- checkpointKey: config.checkpointKey,
391
- }),
392
- };
393
- }
394
- function emitCheckpoint(context, key, action) {
395
- context.eventLog.emit({ type: "checkpoint", spanId: context.spanId, key, action });
396
- }
397
- function checkpointValue(completion, output) {
398
- return completion.kind === "done" ? { kind: "done" } : { kind: "output", value: output };
399
- }
400
- function outputFromCheckpoint(completion, record) {
401
- if (completion.kind === "done" && record.value.kind === "done") {
402
- return undefined;
403
- }
404
- if (completion.kind !== "done" && record.value.kind === "output") {
405
- return record.value.value;
406
- }
407
- throw new AgentConfigError("checkpoint record does not match the requested completion mode");
408
- }
409
- function flightsFor(store) {
410
- const existing = checkpointFlights.get(store);
411
- if (existing !== undefined) {
412
- return existing;
413
- }
414
- const created = new Map();
415
- checkpointFlights.set(store, created);
416
- return created;
417
- }
418
- async function runCheckpointed(config, prompt, completion, sessionFactory, canRetrySession, context, enabled) {
419
- const checkpoint = checkpointContext(config, prompt, completion, enabled);
420
- if (checkpoint === undefined) {
421
- return await driveRun(config, prompt, completion, sessionFactory, canRetrySession, context);
422
- }
423
- let hit;
424
- try {
425
- hit = await checkpoint.store.get(checkpoint.key);
426
- }
427
- catch {
428
- hit = undefined;
429
- }
430
- if (hit !== undefined) {
431
- emitCheckpoint(context, checkpoint.key, "hit");
432
- context.usage.add(hit.usage);
433
- return outputFromCheckpoint(completion, hit);
434
- }
435
- const flights = flightsFor(checkpoint.store);
436
- const active = flights.get(checkpoint.key);
437
- if (active !== undefined) {
438
- emitCheckpoint(context, checkpoint.key, "wait");
439
- const record = await active;
440
- context.usage.add(record.usage);
441
- return outputFromCheckpoint(completion, record);
442
- }
443
- emitCheckpoint(context, checkpoint.key, "miss");
444
- const execute = async () => {
445
- const output = await driveRun(config, prompt, completion, sessionFactory, canRetrySession, context);
446
- const record = {
447
- version: 1,
448
- value: checkpointValue(completion, output),
449
- usage: context.usage.snapshot(),
450
- };
451
- try {
452
- await checkpoint.store.set(checkpoint.key, record);
453
- emitCheckpoint(context, checkpoint.key, "write");
454
- }
455
- catch {
456
- // Checkpoints are an optimization; completed agent output remains authoritative.
457
- }
458
- return record;
459
- };
460
- const flight = execute();
461
- flights.set(checkpoint.key, flight);
462
- try {
463
- const record = await flight;
464
- return outputFromCheckpoint(completion, record);
465
- }
466
- finally {
467
- flights.delete(checkpoint.key);
468
- }
469
- }
470
- async function runWithRetries(config, prompt, sessionFactory, canRetrySession, context, state, request) {
471
- let session = await sessionFactory();
472
- for (let attempt = 0; attempt <= config.retries; attempt += 1) {
473
- try {
474
- const result = await oneBackendTurn(session, request(prompt), context, config.limits.turnDuration);
475
- return { result, session };
476
- }
477
- catch (error) {
478
- const retryable = error instanceof AgentBackendUnavailableError &&
479
- !state.completedUserTool &&
480
- canRetrySession &&
481
- attempt < config.retries;
482
- if (!retryable) {
483
- throw error;
484
- }
485
- session = await sessionFactory();
486
- }
487
- }
488
- throw new AgentBackendUnavailableError("backend produced no result");
489
- }
490
- function combineSignals(signals) {
491
- const [only] = signals;
492
- return signals.length === 1 && only !== undefined ? only : AbortSignal.any([...signals]);
493
- }
494
- function createSignal(controller, parents) {
495
- return combineSignals([controller.signal, ...parents]);
496
- }
497
- function createScopeState(name, path, parent, options = {}) {
498
- if (name.trim().length === 0) {
499
- throw new AgentConfigError("scope name must be non-empty");
500
- }
501
- if (options.retainTraces !== undefined &&
502
- (!Number.isSafeInteger(options.retainTraces) || options.retainTraces < 0)) {
503
- throw new AgentConfigError("scope retainTraces must be a non-negative safe integer");
504
- }
505
- const controller = new AbortController();
506
- const timeout = options.duration === undefined
507
- ? undefined
508
- : AbortSignal.timeout(durationMilliseconds(options.duration));
509
- const inherited = [parent?.signal, options.signal, timeout].filter((signal) => signal !== undefined);
510
- return {
511
- name,
512
- path,
513
- parent,
514
- usage: new UsageAccount(),
515
- traces: [],
516
- retainTraces: options.retainTraces ?? (path.length === 0 ? 0 : DEFAULT_RETAINED_TRACES),
517
- controller,
518
- signal: combineSignals([controller.signal, ...inherited]),
519
- timeout,
520
- duration: options.duration,
521
- activeTargets: new Map(),
522
- pendingObservations: [],
523
- lastTarget: undefined,
524
- };
525
- }
526
- function scopeAncestors(scope) {
527
- const scopes = [];
528
- let current = scope;
529
- while (current !== undefined) {
530
- scopes.push(current);
531
- current = current.parent;
532
- }
533
- return scopes;
534
- }
535
- function emitScopeObservation(log, spanId, observation) {
536
- if (observation.type === "annotate") {
537
- log.emit({ type: "annotate", spanId, attributes: observation.attributes });
538
- }
539
- else {
540
- log.emit({
541
- type: "log",
542
- spanId,
543
- message: observation.message,
544
- level: observation.level,
545
- });
546
- }
547
- }
548
- function observeScope(scope, observation) {
549
- if (scope.activeTargets.size > 0) {
550
- for (const [log, spanId] of scope.activeTargets) {
551
- emitScopeObservation(log, spanId, observation);
552
- }
553
- return;
554
- }
555
- if (scope.lastTarget !== undefined) {
556
- emitScopeObservation(scope.lastTarget.log, scope.lastTarget.spanId, observation);
557
- return;
558
- }
559
- scope.pendingObservations.push(observation);
560
- }
561
- function registerScopeTarget(scope, log, spanId) {
562
- for (const candidate of scopeAncestors(scope)) {
563
- candidate.activeTargets.set(log, spanId);
564
- for (const observation of candidate.pendingObservations.splice(0)) {
565
- emitScopeObservation(log, spanId, observation);
566
- }
567
- }
568
- }
569
- function unregisterScopeTarget(scope, log, spanId) {
570
- for (const candidate of scopeAncestors(scope)) {
571
- candidate.activeTargets.delete(log);
572
- candidate.lastTarget = { log, spanId };
573
- }
574
- }
575
- function registerScopeTrace(scope, log) {
576
- for (const candidate of scopeAncestors(scope)) {
577
- if (candidate.retainTraces === 0) {
578
- continue;
579
- }
580
- candidate.traces.push(log);
581
- if (candidate.traces.length > candidate.retainTraces) {
582
- candidate.traces.splice(0, candidate.traces.length - candidate.retainTraces);
583
- }
584
- }
585
- }
586
- function addScopeUsageOutside(scope, usage, excluded) {
587
- for (const candidate of scopeAncestors(scope)) {
588
- if (!excluded.has(candidate)) {
589
- candidate.usage.add(usage);
590
- }
591
- }
592
- }
593
- function addScopeUsage(scope, usage) {
594
- for (const candidate of scopeAncestors(scope)) {
595
- candidate.usage.add(usage);
596
- }
597
- }
598
- function attachRun(promise, controller, events, usage, log) {
599
- promise.catch(() => undefined);
600
- return {
601
- // biome-ignore lint/suspicious/noThenProperty: AgentRun deliberately implements PromiseLike.
602
- then: promise.then.bind(promise),
603
- events,
604
- get usage() {
605
- return usage.snapshot();
606
- },
607
- get trace() {
608
- return log.snapshot();
609
- },
610
- abort: (reason) => controller.abort(reason),
611
- };
612
- }
613
- function finalizeRun(lifecycle, outcome, error) {
614
- const snapshot = lifecycle.usage.snapshot();
615
- if (lifecycle.parent === undefined) {
616
- addScopeUsage(lifecycle.scope, snapshot);
617
- }
618
- else {
619
- lifecycle.parent.usage.add(snapshot);
620
- if (lifecycle.scope !== lifecycle.parent.scope) {
621
- addScopeUsageOutside(lifecycle.scope, snapshot, new Set(scopeAncestors(lifecycle.parent.scope)));
622
- }
623
- }
624
- lifecycle.onFinalize?.(snapshot);
625
- lifecycle.log.emit({
626
- type: "span_end",
627
- spanId: lifecycle.spanId,
628
- ...(lifecycle.parentSpanId === undefined ? {} : { parentSpanId: lifecycle.parentSpanId }),
629
- durationMs: performance.now() - lifecycle.started,
630
- usage: snapshot,
631
- outcome,
632
- ...(error === undefined ? {} : { error }),
633
- });
634
- if (lifecycle.isRoot) {
635
- unregisterScopeTarget(lifecycle.scope, lifecycle.log, lifecycle.spanId);
636
- lifecycle.log.end();
637
- publishRunControl({
638
- type: "run_end",
639
- traceId: lifecycle.log.traceId,
640
- spanId: lifecycle.spanId,
641
- });
642
- }
643
- }
644
- function normalizedRunError(error, lifecycle) {
645
- const timedOutScope = scopeAncestors(lifecycle.scope).find((scope) => scope.timeout?.aborted === true);
646
- if (timedOutScope?.duration !== undefined) {
647
- return new AgentTimeoutError(`scope ${timedOutScope.path.join("/")} exceeded ${timedOutScope.duration}`, { cause: error });
648
- }
649
- if (lifecycle.signal.aborted) {
650
- return new AgentCancelledError("run was cancelled", { cause: error });
651
- }
652
- return error;
653
- }
654
- async function settleRun(request) {
655
- const { config, context, lifecycle } = request;
656
- let outcome;
657
- try {
658
- const output = await runCheckpointed(config, request.prompt, request.completion, request.sessionFactory, request.canRetrySession, context, request.checkpointEnabled);
659
- const snapshot = lifecycle.usage.snapshot();
660
- const projectedCost = config.limits.budgetUsd === undefined
661
- ? undefined
662
- : knownCost(context.scope.usage.snapshot()) + knownCost(snapshot);
663
- if (config.limits.budgetUsd !== undefined &&
664
- projectedCost !== undefined &&
665
- projectedCost > config.limits.budgetUsd) {
666
- throw new AgentBudgetExceededError(`scope cost $${projectedCost.toFixed(CURRENCY_DECIMALS)} exceeded $${config.limits.budgetUsd.toFixed(CURRENCY_DECIMALS)}`);
667
- }
668
- outcome = { ok: true, output: output };
669
- }
670
- catch (error) {
671
- outcome = { ok: false, error: normalizedRunError(error, lifecycle) };
672
- }
673
- let finalOutcome = "succeeded";
674
- let message;
675
- if (!outcome.ok) {
676
- finalOutcome = outcome.error instanceof AgentCancelledError ? "cancelled" : "failed";
677
- message = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
678
- }
679
- finalizeRun(lifecycle, finalOutcome, message);
680
- if (!outcome.ok) {
681
- throw outcome.error;
682
- }
683
- return {
684
- output: outcome.output,
685
- usage: lifecycle.usage.snapshot(),
686
- trace: lifecycle.log.snapshot(),
687
- };
688
- }
689
- function emitRunOpening(log, spanId, parentSpanId, config, scope, prompt) {
690
- const parent = parentSpanId === undefined ? {} : { parentSpanId };
691
- const scopedName = parentSpanId === undefined ? scope.path.at(-1) : undefined;
692
- log.emit({
693
- type: "span_start",
694
- spanId,
695
- ...parent,
696
- name: scopedName ?? config.name,
697
- kind: "run",
698
- backend: config.backend.name,
699
- model: config.model,
700
- agent: config.name,
701
- ...(scope.path.length === 0 ? {} : { scopePath: scope.path }),
702
- });
703
- log.emit({
704
- type: "system_prompt",
705
- spanId,
706
- ...parent,
707
- text: unigentSystemPrompt(config),
708
- });
709
- log.emit({ type: "user_prompt", spanId, ...parent, text: prompt });
710
- }
711
- function publishRootRunControl(isRoot, log, spanId, controller) {
712
- if (isRoot) {
713
- publishRunControl({
714
- type: "run_start",
715
- traceId: log.traceId,
716
- spanId,
717
- abort: (reason) => controller.abort(reason),
718
- });
719
- }
720
- }
721
- function createRun(request) {
722
- const { config } = request;
723
- const parent = executionContext.getStore();
724
- const isRoot = parent === undefined;
725
- const effectiveScope = parent !== undefined && request.scope.path.length === 0 ? parent.scope : request.scope;
726
- const depth = (parent?.depth ?? -1) + 1;
727
- const maxDepth = config.limits.nestedAgentDepth ?? DEFAULT_NESTED_DEPTH;
728
- if (depth > maxDepth) {
729
- throw new AgentConfigError(`nested agent depth ${depth} exceeds limit ${maxDepth}`);
730
- }
731
- const log = parent?.eventLog ?? new EventLog(randomUUID());
732
- if (isRoot) {
733
- registerScopeTrace(effectiveScope, log);
734
- }
735
- const spanId = randomUUID();
736
- const parentSpanId = parent?.spanId;
737
- const usage = new UsageAccount();
738
- const controller = new AbortController();
739
- const signal = createSignal(controller, [parent?.signal, effectiveScope.signal].filter((candidateSignal) => candidateSignal !== undefined));
740
- const context = {
741
- eventLog: log,
742
- spanId,
743
- usage,
744
- scope: effectiveScope,
745
- signal,
746
- depth,
747
- };
748
- const started = performance.now();
749
- emitRunOpening(log, spanId, parentSpanId, config, effectiveScope, request.prompt);
750
- if (isRoot) {
751
- registerScopeTarget(effectiveScope, log, spanId);
752
- }
753
- const lifecycle = {
754
- log,
755
- spanId,
756
- parentSpanId,
757
- started,
758
- usage,
759
- scope: effectiveScope,
760
- parent,
761
- isRoot,
762
- signal,
763
- onFinalize: request.onFinalize,
764
- };
765
- publishRootRunControl(isRoot, log, spanId, controller);
766
- const promise = settleRun({
767
- config,
768
- prompt: request.prompt,
769
- completion: request.completion,
770
- sessionFactory: request.sessionFactory,
771
- canRetrySession: request.canRetrySession,
772
- context,
773
- lifecycle,
774
- checkpointEnabled: request.checkpointEnabled,
775
- });
776
- return attachRun(promise, controller, log.iterable(spanId), usage, log);
777
- }
778
- function rejectedRun(error) {
779
- const controller = new AbortController();
780
- const log = new EventLog(randomUUID());
781
- const usage = new UsageAccount();
782
- log.end();
783
- return attachRun(Promise.reject(error), controller, log.iterable(), usage, log);
784
- }
785
- async function openedSession(config, state) {
786
- if (state.opened !== undefined) {
787
- return state.opened;
788
- }
789
- const pending = state.opening ??
790
- Promise.resolve(config.backend.openSession({ model: config.model })).then((session) => {
791
- state.opened = session;
792
- return session;
793
- });
794
- state.opening = pending;
795
- try {
796
- return await pending;
797
- }
798
- catch (error) {
799
- if (state.opening === pending) {
800
- state.opening = undefined;
801
- }
802
- throw error;
803
- }
804
- }
805
- function makeSession(config, scope, seed) {
806
- const state = { opened: seed, opening: undefined };
807
- let busy = false;
808
- const usage = new UsageAccount();
809
- const run = (prompt, requested) => {
810
- if (busy) {
811
- throw new AgentConcurrencyError("stateful sessions are single-flight; fork before parallel turns");
812
- }
813
- let completion;
814
- try {
815
- completion = resolveCompletion(requested);
816
- }
817
- catch (error) {
818
- return rejectedRun(error);
819
- }
820
- busy = true;
821
- try {
822
- return createRun({
823
- config: { ...config, retries: 0 },
824
- scope,
825
- prompt,
826
- completion,
827
- sessionFactory: async () => await openedSession(config, state),
828
- canRetrySession: false,
829
- checkpointEnabled: false,
830
- onFinalize: (runUsage) => {
831
- usage.add(runUsage);
832
- busy = false;
833
- },
834
- });
835
- }
836
- catch (error) {
837
- busy = false;
838
- return rejectedRun(error);
839
- }
840
- };
841
- return {
842
- run,
843
- fork: () => {
844
- if (busy) {
845
- throw new AgentConcurrencyError("cannot fork a session during an active turn");
846
- }
847
- if (state.opened === undefined) {
848
- throw new AgentConfigError("run at least one session turn before forking");
849
- }
850
- if (state.opened.fork === undefined) {
851
- throw new AgentConfigError(`${config.backend.name} does not support session forks`);
852
- }
853
- return makeSession(config, scope, state.opened.fork());
854
- },
855
- get usage() {
856
- return usage.snapshot();
857
- },
858
- };
859
- }
860
- function isDone(requested) {
861
- return "kind" in requested && requested.kind === "done";
862
- }
863
- function isOutputSchema(requested) {
864
- return "~standard" in requested;
865
- }
866
- function resolveCompletion(requested) {
867
- if (requested === undefined) {
868
- return { kind: "prose" };
869
- }
870
- if (typeof requested !== "object" || requested === null || Array.isArray(requested)) {
871
- throw new AgentConfigError("run completion must be done or a Standard Schema object");
872
- }
873
- if (isDone(requested)) {
874
- return { kind: "done" };
875
- }
876
- if (!isOutputSchema(requested)) {
877
- throw new AgentConfigError("run completion must be done or a Standard Schema object");
878
- }
879
- return { kind: "schema", schema: requested };
880
- }
881
- function makeAgent(config, scope, scoped = false) {
882
- const ownScope = scope ?? createScopeState(config.name, [], undefined);
883
- const run = (prompt, requested) => {
884
- try {
885
- return createRun({
886
- config,
887
- scope: ownScope,
888
- prompt,
889
- completion: resolveCompletion(requested),
890
- sessionFactory: async () => await config.backend.openSession({ model: config.model }),
891
- canRetrySession: true,
892
- checkpointEnabled: true,
893
- });
894
- }
895
- catch (error) {
896
- return rejectedRun(error);
897
- }
898
- };
899
- return {
900
- with: (overrides) => makeAgent(mergeConfig(config, overrides), ownScope, scoped),
901
- scope: (name, options = {}) => {
902
- const parent = scoped ? ownScope : undefined;
903
- const path = parent === undefined ? [name] : [...parent.path, name];
904
- const scopedState = createScopeState(name, path, parent, options);
905
- return makeScope(mergeConfig(config, options), scopedState);
906
- },
907
- session: () => makeSession(config, ownScope),
908
- run,
909
- };
910
- }
911
- function makeScope(config, scope) {
912
- const handle = makeAgent(config, scope, true);
913
- return {
914
- ...handle,
915
- name: scope.name,
916
- path: scope.path,
917
- abort: (reason) => scope.controller.abort(reason),
918
- annotate: (attributes) => observeScope(scope, { type: "annotate", attributes }),
919
- log: (message, level = "info") => observeScope(scope, { type: "log", message, level }),
920
- get usage() {
921
- return scope.usage.snapshot();
922
- },
923
- get traces() {
924
- return scope.traces.map((trace) => trace.snapshot());
925
- },
926
- };
927
- }
928
- function isPortableTool(value) {
929
- return typeof value !== "function" && value.kind === "tool";
930
- }
931
- function isFailTool(value) {
932
- return typeof value !== "function" && value.kind === "fail";
933
- }
934
- function resolveTools(options) {
935
- const tools = options.tools ?? [];
936
- const functions = tools.filter((value) => typeof value === "function");
937
- if (functions.length > 0 && options.source === undefined) {
938
- throw new AgentConfigError("source is required when exposing source-derived function tools");
939
- }
940
- const sourceTools = options.source === undefined ? [] : compileSourceTools(options.source, functions);
941
- const portableTools = tools.filter(isPortableTool).map(compilePortableTool);
942
- const compiledTools = [...sourceTools, ...portableTools];
943
- const names = new Set();
944
- for (const compiled of compiledTools) {
945
- if (compiled.name.startsWith(RESERVED_TOOL_PREFIX)) {
946
- throw new AgentConfigError(`tool name uses reserved ${RESERVED_TOOL_PREFIX} namespace: ${compiled.name}`);
947
- }
948
- if (names.has(compiled.name)) {
949
- throw new AgentConfigError(`duplicate tool name: ${compiled.name}`);
950
- }
951
- names.add(compiled.name);
952
- }
953
- return { compiledTools, hasFailTool: tools.some(isFailTool) };
954
- }
955
- /** Create an immutable universal agent handle. */
956
- function agent(options) {
957
- if (options.name.trim().length === 0 || options.model.trim().length === 0) {
958
- throw new AgentConfigError("agent name and model must be non-empty");
959
- }
960
- const compiled = resolveTools(options);
961
- return makeAgent(validateResolvedConfig({
962
- name: options.name,
963
- backend: options.backend,
964
- model: options.model,
965
- thinking: options.thinking,
966
- systemPrompt: options.systemPrompt,
967
- ...compiled,
968
- retries: options.retries ?? 0,
969
- repairAttempts: options.repairAttempts ?? DEFAULT_REPAIR_ATTEMPTS,
970
- limits: options.limits ?? {},
971
- checkpoint: options.checkpoint,
972
- checkpointKey: options.checkpointKey,
973
- }));
974
- }
975
- export { agent };
976
- //# sourceMappingURL=runtime.js.map