ai-functions 2.1.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -4
- package/CHANGELOG.md +68 -1
- package/README.md +397 -157
- package/dist/ai-promise.d.ts +50 -3
- package/dist/ai-promise.d.ts.map +1 -1
- package/dist/ai-promise.js +410 -51
- package/dist/ai-promise.js.map +1 -1
- package/dist/ai-schemas.d.ts +56 -0
- package/dist/ai-schemas.d.ts.map +1 -0
- package/dist/ai-schemas.js +53 -0
- package/dist/ai-schemas.js.map +1 -0
- package/dist/ai.d.ts +16 -242
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +54 -837
- package/dist/ai.js.map +1 -1
- package/dist/batch/anthropic.d.ts +6 -4
- package/dist/batch/anthropic.d.ts.map +1 -1
- package/dist/batch/anthropic.js +83 -145
- package/dist/batch/anthropic.js.map +1 -1
- package/dist/batch/bedrock.d.ts +8 -30
- package/dist/batch/bedrock.d.ts.map +1 -1
- package/dist/batch/bedrock.js +155 -338
- package/dist/batch/bedrock.js.map +1 -1
- package/dist/batch/cloudflare.d.ts +8 -20
- package/dist/batch/cloudflare.d.ts.map +1 -1
- package/dist/batch/cloudflare.js +68 -189
- package/dist/batch/cloudflare.js.map +1 -1
- package/dist/batch/google.d.ts +6 -20
- package/dist/batch/google.d.ts.map +1 -1
- package/dist/batch/google.js +70 -238
- package/dist/batch/google.js.map +1 -1
- package/dist/batch/index.d.ts +4 -1
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +4 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/memory.d.ts +1 -1
- package/dist/batch/memory.d.ts.map +1 -1
- package/dist/batch/memory.js +14 -10
- package/dist/batch/memory.js.map +1 -1
- package/dist/batch/openai.d.ts +11 -14
- package/dist/batch/openai.d.ts.map +1 -1
- package/dist/batch/openai.js +52 -156
- package/dist/batch/openai.js.map +1 -1
- package/dist/batch/provider.d.ts +111 -0
- package/dist/batch/provider.d.ts.map +1 -0
- package/dist/batch/provider.js +233 -0
- package/dist/batch/provider.js.map +1 -0
- package/dist/batch-map.d.ts.map +1 -1
- package/dist/batch-map.js +23 -17
- package/dist/batch-map.js.map +1 -1
- package/dist/batch-queue.d.ts +65 -0
- package/dist/batch-queue.d.ts.map +1 -1
- package/dist/batch-queue.js +169 -14
- package/dist/batch-queue.js.map +1 -1
- package/dist/budget.d.ts +272 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +513 -0
- package/dist/budget.js.map +1 -0
- package/dist/cache.d.ts +295 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +433 -0
- package/dist/cache.js.map +1 -0
- package/dist/context.d.ts +42 -8
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +64 -62
- package/dist/context.js.map +1 -1
- package/dist/digital-objects-registry.d.ts +229 -0
- package/dist/digital-objects-registry.d.ts.map +1 -0
- package/dist/digital-objects-registry.js +617 -0
- package/dist/digital-objects-registry.js.map +1 -0
- package/dist/embeddings.d.ts +2 -2
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval/runner.d.ts +10 -1
- package/dist/eval/runner.d.ts.map +1 -1
- package/dist/eval/runner.js +41 -35
- package/dist/eval/runner.js.map +1 -1
- package/dist/eval-log/in-memory.d.ts +34 -0
- package/dist/eval-log/in-memory.d.ts.map +1 -0
- package/dist/eval-log/in-memory.js +84 -0
- package/dist/eval-log/in-memory.js.map +1 -0
- package/dist/eval-log/index.d.ts +29 -0
- package/dist/eval-log/index.d.ts.map +1 -0
- package/dist/eval-log/index.js +39 -0
- package/dist/eval-log/index.js.map +1 -0
- package/dist/eval-log/types.d.ts +101 -0
- package/dist/eval-log/types.d.ts.map +1 -0
- package/dist/eval-log/types.js +16 -0
- package/dist/eval-log/types.js.map +1 -0
- package/dist/function-registry.d.ts +116 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +546 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/generate.d.ts +9 -3
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +18 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +35 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -42
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +118 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +187 -0
- package/dist/logger.js.map +1 -0
- package/dist/middleware/budget.d.ts +84 -0
- package/dist/middleware/budget.d.ts.map +1 -0
- package/dist/middleware/budget.js +110 -0
- package/dist/middleware/budget.js.map +1 -0
- package/dist/middleware/cache.d.ts +103 -0
- package/dist/middleware/cache.d.ts.map +1 -0
- package/dist/middleware/cache.js +228 -0
- package/dist/middleware/cache.js.map +1 -0
- package/dist/middleware/embed-cache.d.ts +99 -0
- package/dist/middleware/embed-cache.d.ts.map +1 -0
- package/dist/middleware/embed-cache.js +128 -0
- package/dist/middleware/embed-cache.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/trace.d.ts +103 -0
- package/dist/middleware/trace.d.ts.map +1 -0
- package/dist/middleware/trace.js +176 -0
- package/dist/middleware/trace.js.map +1 -0
- package/dist/primitives.d.ts +120 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +398 -26
- package/dist/primitives.js.map +1 -1
- package/dist/retry.d.ts +368 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +646 -0
- package/dist/retry.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -10
- package/dist/schema.js.map +1 -1
- package/dist/telemetry.d.ts +128 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +285 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/template.d.ts.map +1 -1
- package/dist/template.js +6 -1
- package/dist/template.js.map +1 -1
- package/dist/tool-orchestration.d.ts +453 -0
- package/dist/tool-orchestration.d.ts.map +1 -0
- package/dist/tool-orchestration.js +763 -0
- package/dist/tool-orchestration.js.map +1 -0
- package/dist/type-guards.d.ts +28 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +29 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +135 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -1
- package/dist/types.js.map +1 -1
- package/dist/wrap-for-v3.d.ts +80 -0
- package/dist/wrap-for-v3.d.ts.map +1 -0
- package/dist/wrap-for-v3.js +89 -0
- package/dist/wrap-for-v3.js.map +1 -0
- package/examples/00-quickstart.ts +232 -0
- package/examples/01-rag-chatbot.ts +212 -0
- package/examples/02-multi-agent-research.ts +290 -0
- package/examples/03-email-classification.ts +379 -0
- package/examples/04-content-moderation.ts +400 -0
- package/examples/05-document-extraction.ts +455 -0
- package/examples/06-streaming-chat-nextjs.ts +437 -0
- package/examples/07-cloudflare-worker.ts +483 -0
- package/examples/08-batch-processing.ts +491 -0
- package/examples/09-budget-constrained.ts +527 -0
- package/examples/10-tool-orchestration.ts +565 -0
- package/examples/11-retry-resilience.ts +403 -0
- package/examples/12-caching-strategies.ts +422 -0
- package/examples/README.md +145 -0
- package/package.json +10 -6
- package/src/ai-promise.ts +528 -99
- package/src/ai-schemas.ts +122 -0
- package/src/ai.ts +69 -1153
- package/src/batch/anthropic.ts +96 -161
- package/src/batch/bedrock.ts +203 -454
- package/src/batch/cloudflare.ts +99 -282
- package/src/batch/google.ts +91 -297
- package/src/batch/index.ts +4 -1
- package/src/batch/memory.ts +15 -10
- package/src/batch/openai.ts +65 -193
- package/src/batch/provider.ts +336 -0
- package/src/batch-map.ts +29 -24
- package/src/batch-queue.ts +200 -11
- package/src/budget.ts +740 -0
- package/src/cache.ts +681 -0
- package/src/context.ts +122 -76
- package/src/digital-objects-registry.ts +750 -0
- package/src/errors.ts +37 -0
- package/src/eval/runner.ts +63 -38
- package/src/eval-log/in-memory.ts +90 -0
- package/src/eval-log/index.ts +46 -0
- package/src/eval-log/types.ts +110 -0
- package/src/function-registry.ts +671 -0
- package/src/generate.ts +33 -33
- package/src/index.ts +325 -49
- package/src/logger.ts +232 -0
- package/src/middleware/budget.ts +171 -0
- package/src/middleware/cache.ts +299 -0
- package/src/middleware/embed-cache.ts +195 -0
- package/src/middleware/index.ts +23 -0
- package/src/middleware/trace.ts +248 -0
- package/src/primitives.ts +589 -62
- package/src/retry.ts +902 -0
- package/src/schema.ts +8 -17
- package/src/telemetry.ts +403 -0
- package/src/template.ts +8 -4
- package/src/tool-orchestration.ts +1173 -0
- package/src/type-guards.ts +31 -0
- package/src/types.ts +164 -25
- package/src/wrap-for-v3.ts +105 -0
- package/test/ai-promise.test.ts +1080 -0
- package/test/ai-proxy.test.ts +1 -1
- package/test/backward-compat.test.ts +147 -0
- package/test/batch-autosubmit-errors.test.ts +610 -0
- package/test/batch-blog-posts.test.ts +87 -129
- package/test/budget-tracking.test.ts +800 -0
- package/test/cache.test.ts +712 -0
- package/test/context-isolation.test.ts +687 -0
- package/test/core-functions.test.ts +183 -579
- package/test/decide.test.ts +154 -322
- package/test/define.test.ts +211 -8
- package/test/digital-objects-registry.test.ts +760 -0
- package/test/embedding-cache-middleware.test.ts +140 -0
- package/test/evals/deterministic.eval.test.ts +376 -0
- package/test/generate-core.test.ts +140 -229
- package/test/implicit-batch.test.ts +22 -65
- package/test/json-parse-error-handling.test.ts +463 -0
- package/test/retry-policy-integration.test.ts +117 -0
- package/test/retry.test.ts +1016 -0
- package/test/schema.test.ts +55 -19
- package/test/streaming.test.ts +316 -0
- package/test/template.test.ts +1164 -0
- package/test/tool-orchestration.test.ts +1040 -0
- package/test/wrap-for-v3.test.ts +612 -0
- package/vitest.config.js +6 -0
- package/vitest.config.ts +20 -0
- package/dist/rpc/auth.d.ts +0 -69
- package/dist/rpc/auth.d.ts.map +0 -1
- package/dist/rpc/auth.js +0 -136
- package/dist/rpc/auth.js.map +0 -1
- package/dist/rpc/client.d.ts +0 -62
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -103
- package/dist/rpc/client.js.map +0 -1
- package/dist/rpc/deferred.d.ts +0 -60
- package/dist/rpc/deferred.d.ts.map +0 -1
- package/dist/rpc/deferred.js +0 -96
- package/dist/rpc/deferred.js.map +0 -1
- package/dist/rpc/index.d.ts +0 -22
- package/dist/rpc/index.d.ts.map +0 -1
- package/dist/rpc/index.js +0 -38
- package/dist/rpc/index.js.map +0 -1
- package/dist/rpc/local.d.ts +0 -42
- package/dist/rpc/local.d.ts.map +0 -1
- package/dist/rpc/local.js +0 -50
- package/dist/rpc/local.js.map +0 -1
- package/dist/rpc/server.d.ts +0 -165
- package/dist/rpc/server.d.ts.map +0 -1
- package/dist/rpc/server.js +0 -405
- package/dist/rpc/server.js.map +0 -1
- package/dist/rpc/session.d.ts +0 -32
- package/dist/rpc/session.d.ts.map +0 -1
- package/dist/rpc/session.js +0 -43
- package/dist/rpc/session.js.map +0 -1
- package/dist/rpc/transport.d.ts +0 -306
- package/dist/rpc/transport.d.ts.map +0 -1
- package/dist/rpc/transport.js +0 -731
- package/dist/rpc/transport.js.map +0 -1
- package/src/batch/anthropic.js +0 -256
- package/src/batch/bedrock.js +0 -584
- package/src/batch/cloudflare.js +0 -287
- package/src/batch/google.js +0 -359
- package/src/batch/index.js +0 -30
- package/src/batch/memory.js +0 -187
- package/src/batch/openai.js +0 -402
- package/src/eval/index.js +0 -7
- package/src/eval/models.js +0 -119
- package/src/eval/runner.js +0 -147
- package/test/schema.test.js +0 -96
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic Tool Orchestration
|
|
3
|
+
*
|
|
4
|
+
* Provides multi-turn model→tools→model loop orchestration for complex AI workflows.
|
|
5
|
+
*
|
|
6
|
+
* Key components:
|
|
7
|
+
* - AgenticLoop: Orchestrates multi-turn conversations with tool execution
|
|
8
|
+
* - ToolRouter: Routes tool calls to registered handlers
|
|
9
|
+
* - ToolValidator: Validates tool arguments before execution
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// ToolValidator
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Validates tool arguments before execution
|
|
19
|
+
*/
|
|
20
|
+
export class ToolValidator {
|
|
21
|
+
tools = new Map();
|
|
22
|
+
/**
|
|
23
|
+
* Register a tool for validation
|
|
24
|
+
*/
|
|
25
|
+
register(tool) {
|
|
26
|
+
this.tools.set(tool.name, tool);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validate arguments for a tool
|
|
30
|
+
*/
|
|
31
|
+
validate(toolName, args) {
|
|
32
|
+
const tool = this.tools.get(toolName);
|
|
33
|
+
if (!tool) {
|
|
34
|
+
return {
|
|
35
|
+
valid: false,
|
|
36
|
+
errors: [`Tool '${toolName}' not registered`],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = tool.parameters.parse(args);
|
|
41
|
+
return {
|
|
42
|
+
valid: true,
|
|
43
|
+
parsedArgs: parsed,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof z.ZodError) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
errors: error.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
valid: false,
|
|
55
|
+
errors: [error.message],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validate multiple tool calls at once
|
|
61
|
+
*/
|
|
62
|
+
validateAll(calls) {
|
|
63
|
+
return calls.map((call) => this.validate(call.name, call.arguments));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// ToolRouter
|
|
68
|
+
// ============================================================================
|
|
69
|
+
/**
|
|
70
|
+
* Routes tool calls to registered handlers
|
|
71
|
+
*
|
|
72
|
+
* @deprecated Phase C Week 2 — `ToolRouter` has zero production callers in
|
|
73
|
+
* primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
|
|
74
|
+
* `ai-primitives` umbrella re-export tests reference it. AI SDK 6's native
|
|
75
|
+
* tool-routing under `generateText({ tools })` and `Agent` / `ToolLoopAgent`
|
|
76
|
+
* cover the same surface. Will be removed in the Phase C semver bump
|
|
77
|
+
* alongside `AgenticLoop` and `createAgenticLoop`.
|
|
78
|
+
*/
|
|
79
|
+
export class ToolRouter {
|
|
80
|
+
tools = new Map();
|
|
81
|
+
validator = new ToolValidator();
|
|
82
|
+
/**
|
|
83
|
+
* Register a tool
|
|
84
|
+
*/
|
|
85
|
+
register(tool) {
|
|
86
|
+
this.tools.set(tool.name, tool);
|
|
87
|
+
this.validator.register(tool);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Route a single tool call
|
|
91
|
+
*/
|
|
92
|
+
async route(call) {
|
|
93
|
+
const tool = this.tools.get(call.name);
|
|
94
|
+
if (!tool) {
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
error: `Tool '${call.name}' not found`,
|
|
98
|
+
toolCall: call,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const validation = this.validator.validate(call.name, call.arguments);
|
|
102
|
+
if (!validation.valid) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: `Validation failed: ${validation.errors?.join(', ')}`,
|
|
106
|
+
toolCall: call,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const result = await tool.execute(validation.parsedArgs);
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
result,
|
|
114
|
+
toolCall: call,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: error.message,
|
|
121
|
+
toolCall: call,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Route multiple tool calls sequentially
|
|
127
|
+
*/
|
|
128
|
+
async routeAll(calls) {
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const call of calls) {
|
|
131
|
+
results.push(await this.route(call));
|
|
132
|
+
}
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Route multiple tool calls in parallel
|
|
137
|
+
*/
|
|
138
|
+
async routeAllParallel(calls) {
|
|
139
|
+
return Promise.all(calls.map((call) => this.route(call)));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Format a tool result for model consumption
|
|
143
|
+
*/
|
|
144
|
+
formatResult(result) {
|
|
145
|
+
if (result.success) {
|
|
146
|
+
return {
|
|
147
|
+
role: 'tool',
|
|
148
|
+
content: JSON.stringify(result.result),
|
|
149
|
+
...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
role: 'tool',
|
|
154
|
+
content: JSON.stringify({ error: result.error }),
|
|
155
|
+
...(result.toolCall?.id !== undefined && { tool_call_id: result.toolCall.id }),
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// ============================================================================
|
|
161
|
+
// AgenticLoop
|
|
162
|
+
// ============================================================================
|
|
163
|
+
/**
|
|
164
|
+
* Orchestrates multi-turn model→tools→model loops
|
|
165
|
+
*
|
|
166
|
+
* @deprecated Phase C Week 2 — `AgenticLoop` has zero production callers in
|
|
167
|
+
* primitives.org.ai (audited 2026-05-06; see `bd show aip-ibid`). Only the
|
|
168
|
+
* `ai-primitives` umbrella re-export tests reference it. The production
|
|
169
|
+
* cascade walker (`services-as-software/v3/invoke/cascade-walker.ts:178`)
|
|
170
|
+
* already uses AI SDK 6's `generateText({ tools, maxSteps: 10 })` directly
|
|
171
|
+
* for agentic steps — no consumer code paths through this class. AI SDK 6's
|
|
172
|
+
* `Agent` / `ToolLoopAgent` (`stopWhen: stepCountIs(N)`) are the going-
|
|
173
|
+
* forward primitives. Will be removed in the Phase C semver bump.
|
|
174
|
+
*/
|
|
175
|
+
export class AgenticLoop {
|
|
176
|
+
options;
|
|
177
|
+
router;
|
|
178
|
+
validator;
|
|
179
|
+
constructor(options) {
|
|
180
|
+
this.options = {
|
|
181
|
+
strictMaxSteps: false,
|
|
182
|
+
parallelExecution: false,
|
|
183
|
+
maxParallelCalls: 10,
|
|
184
|
+
retryFailedTools: false,
|
|
185
|
+
maxToolRetries: 3,
|
|
186
|
+
continueOnError: false,
|
|
187
|
+
trackUsage: false,
|
|
188
|
+
...options,
|
|
189
|
+
};
|
|
190
|
+
this.router = new ToolRouter();
|
|
191
|
+
this.validator = new ToolValidator();
|
|
192
|
+
// Register all tools
|
|
193
|
+
for (const tool of options.tools) {
|
|
194
|
+
this.router.register(tool);
|
|
195
|
+
this.validator.register(tool);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get tools in AI SDK format
|
|
200
|
+
*/
|
|
201
|
+
getToolsForSDK() {
|
|
202
|
+
const tools = {};
|
|
203
|
+
for (const tool of this.options.tools) {
|
|
204
|
+
tools[tool.name] = {
|
|
205
|
+
description: tool.description,
|
|
206
|
+
parameters: tool.parameters,
|
|
207
|
+
execute: tool.execute,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return tools;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Execute a tool call with timeout and retry support
|
|
214
|
+
*/
|
|
215
|
+
async executeToolCall(call, abortSignal) {
|
|
216
|
+
const { toolTimeout, retryFailedTools, maxToolRetries = 3 } = this.options;
|
|
217
|
+
let lastError;
|
|
218
|
+
let retryCount = 0;
|
|
219
|
+
const maxAttempts = retryFailedTools ? maxToolRetries : 1;
|
|
220
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
221
|
+
// Check abort signal
|
|
222
|
+
if (abortSignal?.aborted) {
|
|
223
|
+
throw new Error('Aborted');
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
// Create a promise for the tool execution
|
|
227
|
+
const executePromise = this.router.route(call);
|
|
228
|
+
// Apply timeout if configured
|
|
229
|
+
let result;
|
|
230
|
+
if (toolTimeout) {
|
|
231
|
+
let timeoutId;
|
|
232
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
233
|
+
timeoutId = setTimeout(() => reject(new Error('Tool execution timeout')), toolTimeout);
|
|
234
|
+
});
|
|
235
|
+
try {
|
|
236
|
+
result = await Promise.race([executePromise, timeoutPromise]);
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
clearTimeout(timeoutId);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
result = await executePromise;
|
|
244
|
+
}
|
|
245
|
+
if (result.success) {
|
|
246
|
+
return {
|
|
247
|
+
name: call.name,
|
|
248
|
+
arguments: call.arguments,
|
|
249
|
+
result: result.result,
|
|
250
|
+
retryCount,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
lastError = result.error;
|
|
254
|
+
retryCount = attempt + 1;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
lastError = error.message;
|
|
258
|
+
if (lastError === 'Aborted')
|
|
259
|
+
throw error;
|
|
260
|
+
retryCount = attempt + 1;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
name: call.name,
|
|
265
|
+
arguments: call.arguments,
|
|
266
|
+
...(lastError !== undefined && { error: lastError }),
|
|
267
|
+
retryCount: retryCount > 0 ? retryCount - 1 : 0,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Execute multiple tool calls
|
|
272
|
+
*/
|
|
273
|
+
async executeToolCalls(calls, abortSignal) {
|
|
274
|
+
const { parallelExecution, maxParallelCalls = 10 } = this.options;
|
|
275
|
+
if (!parallelExecution) {
|
|
276
|
+
// Sequential execution
|
|
277
|
+
const results = [];
|
|
278
|
+
for (const call of calls) {
|
|
279
|
+
results.push(await this.executeToolCall(call, abortSignal));
|
|
280
|
+
}
|
|
281
|
+
return results;
|
|
282
|
+
}
|
|
283
|
+
// Parallel execution with concurrency limit
|
|
284
|
+
const results = [];
|
|
285
|
+
const chunks = [];
|
|
286
|
+
for (let i = 0; i < calls.length; i += maxParallelCalls) {
|
|
287
|
+
chunks.push(calls.slice(i, i + maxParallelCalls));
|
|
288
|
+
}
|
|
289
|
+
for (const chunk of chunks) {
|
|
290
|
+
const chunkResults = await Promise.all(chunk.map((call) => this.executeToolCall(call, abortSignal)));
|
|
291
|
+
results.push(...chunkResults);
|
|
292
|
+
}
|
|
293
|
+
return results;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Build messages for the next model call
|
|
297
|
+
*/
|
|
298
|
+
buildMessages(prompt, system, conversationMessages, toolResults) {
|
|
299
|
+
const messages = [];
|
|
300
|
+
// Add system message if provided
|
|
301
|
+
if (system) {
|
|
302
|
+
messages.push({ role: 'system', content: system });
|
|
303
|
+
}
|
|
304
|
+
// Add conversation history
|
|
305
|
+
messages.push(...conversationMessages);
|
|
306
|
+
// Add tool results as tool messages
|
|
307
|
+
for (const result of toolResults) {
|
|
308
|
+
if (result.error) {
|
|
309
|
+
messages.push({
|
|
310
|
+
role: 'tool',
|
|
311
|
+
content: JSON.stringify({ error: result.error }),
|
|
312
|
+
isError: true,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
messages.push({
|
|
317
|
+
role: 'tool',
|
|
318
|
+
content: JSON.stringify(result.result),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return messages;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Run the agentic loop
|
|
326
|
+
*/
|
|
327
|
+
async run(runOptions) {
|
|
328
|
+
const { model, prompt, system, abortSignal } = runOptions;
|
|
329
|
+
const { maxSteps, strictMaxSteps, continueOnError, trackUsage, onStep } = this.options;
|
|
330
|
+
const allToolCalls = [];
|
|
331
|
+
const allToolResults = [];
|
|
332
|
+
const messages = [{ role: 'user', content: prompt }];
|
|
333
|
+
let steps = 0;
|
|
334
|
+
let stopReason = 'stop';
|
|
335
|
+
let finalText = '';
|
|
336
|
+
let totalUsage = trackUsage
|
|
337
|
+
? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
338
|
+
: undefined;
|
|
339
|
+
try {
|
|
340
|
+
while (steps < maxSteps) {
|
|
341
|
+
// Check abort signal
|
|
342
|
+
if (abortSignal?.aborted) {
|
|
343
|
+
stopReason = 'aborted';
|
|
344
|
+
throw new Error('Aborted');
|
|
345
|
+
}
|
|
346
|
+
steps++;
|
|
347
|
+
// Call the model
|
|
348
|
+
const response = await model.generate({
|
|
349
|
+
messages: this.buildMessages(prompt, system, messages.slice(1), []),
|
|
350
|
+
tools: this.getToolsForSDK(),
|
|
351
|
+
});
|
|
352
|
+
// Track usage
|
|
353
|
+
if (trackUsage && response.usage) {
|
|
354
|
+
totalUsage.promptTokens += response.usage.promptTokens;
|
|
355
|
+
totalUsage.completionTokens += response.usage.completionTokens;
|
|
356
|
+
totalUsage.totalTokens += response.usage.totalTokens;
|
|
357
|
+
}
|
|
358
|
+
// If no tool calls, we're done
|
|
359
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
360
|
+
finalText = response.text || '';
|
|
361
|
+
messages.push({ role: 'assistant', content: finalText });
|
|
362
|
+
stopReason = 'stop';
|
|
363
|
+
if (onStep) {
|
|
364
|
+
onStep({
|
|
365
|
+
stepNumber: steps,
|
|
366
|
+
toolCalls: [],
|
|
367
|
+
response,
|
|
368
|
+
messages: [...messages],
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
// Execute tool calls
|
|
374
|
+
const toolResults = await this.executeToolCalls(response.toolCalls, abortSignal);
|
|
375
|
+
// Check for errors
|
|
376
|
+
const hasErrors = toolResults.some((r) => r.error);
|
|
377
|
+
if (hasErrors && !continueOnError) {
|
|
378
|
+
// Still record the results but note the errors
|
|
379
|
+
}
|
|
380
|
+
// Record tool calls and results
|
|
381
|
+
for (const result of toolResults) {
|
|
382
|
+
allToolCalls.push(result);
|
|
383
|
+
allToolResults.push({
|
|
384
|
+
toolName: result.name,
|
|
385
|
+
result: result.result,
|
|
386
|
+
});
|
|
387
|
+
// Add tool result to messages
|
|
388
|
+
if (result.error) {
|
|
389
|
+
messages.push({
|
|
390
|
+
role: 'tool',
|
|
391
|
+
content: JSON.stringify({ error: result.error }),
|
|
392
|
+
isError: true,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
messages.push({
|
|
397
|
+
role: 'tool',
|
|
398
|
+
content: JSON.stringify(result.result),
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Call onStep callback
|
|
403
|
+
if (onStep) {
|
|
404
|
+
onStep({
|
|
405
|
+
stepNumber: steps,
|
|
406
|
+
toolCalls: response.toolCalls.map((tc, i) => ({
|
|
407
|
+
...tc,
|
|
408
|
+
...(toolResults[i]?.result !== undefined && { result: toolResults[i]?.result }),
|
|
409
|
+
...(toolResults[i]?.error !== undefined && { error: toolResults[i]?.error }),
|
|
410
|
+
})),
|
|
411
|
+
response,
|
|
412
|
+
messages: [...messages],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// Add assistant message with tool calls
|
|
416
|
+
messages.push({
|
|
417
|
+
role: 'assistant',
|
|
418
|
+
content: '',
|
|
419
|
+
tool_calls: response.toolCalls,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
// Check if we hit max steps
|
|
423
|
+
if (steps >= maxSteps && stopReason === 'stop') {
|
|
424
|
+
stopReason = 'max_steps';
|
|
425
|
+
if (strictMaxSteps) {
|
|
426
|
+
throw new Error('Max steps exceeded');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (error.message === 'Aborted') {
|
|
432
|
+
stopReason = 'aborted';
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
if (error.message === 'Max steps exceeded') {
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
stopReason = 'error';
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
text: finalText,
|
|
443
|
+
steps,
|
|
444
|
+
toolCalls: allToolCalls,
|
|
445
|
+
toolResults: allToolResults,
|
|
446
|
+
stopReason,
|
|
447
|
+
...(totalUsage !== undefined && { usage: totalUsage }),
|
|
448
|
+
messages,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Run the agentic loop with streaming support
|
|
453
|
+
*
|
|
454
|
+
* Returns an async generator that yields step events as they occur.
|
|
455
|
+
*/
|
|
456
|
+
async *stream(runOptions) {
|
|
457
|
+
const { model, prompt, system, abortSignal } = runOptions;
|
|
458
|
+
const { maxSteps, strictMaxSteps, continueOnError, trackUsage } = this.options;
|
|
459
|
+
const allToolCalls = [];
|
|
460
|
+
const allToolResults = [];
|
|
461
|
+
const messages = [{ role: 'user', content: prompt }];
|
|
462
|
+
let steps = 0;
|
|
463
|
+
let stopReason = 'stop';
|
|
464
|
+
let finalText = '';
|
|
465
|
+
let totalUsage = trackUsage
|
|
466
|
+
? { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
467
|
+
: undefined;
|
|
468
|
+
yield { type: 'start', prompt, timestamp: Date.now() };
|
|
469
|
+
try {
|
|
470
|
+
while (steps < maxSteps) {
|
|
471
|
+
if (abortSignal?.aborted) {
|
|
472
|
+
yield { type: 'aborted', steps, timestamp: Date.now() };
|
|
473
|
+
throw new Error('Aborted');
|
|
474
|
+
}
|
|
475
|
+
steps++;
|
|
476
|
+
yield { type: 'step_start', stepNumber: steps, timestamp: Date.now() };
|
|
477
|
+
const response = await model.generate({
|
|
478
|
+
messages: this.buildMessages(prompt, system, messages.slice(1), []),
|
|
479
|
+
tools: this.getToolsForSDK(),
|
|
480
|
+
});
|
|
481
|
+
if (trackUsage && response.usage) {
|
|
482
|
+
totalUsage.promptTokens += response.usage.promptTokens;
|
|
483
|
+
totalUsage.completionTokens += response.usage.completionTokens;
|
|
484
|
+
totalUsage.totalTokens += response.usage.totalTokens;
|
|
485
|
+
}
|
|
486
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
487
|
+
finalText = response.text || '';
|
|
488
|
+
messages.push({ role: 'assistant', content: finalText });
|
|
489
|
+
yield { type: 'text', text: finalText, stepNumber: steps, timestamp: Date.now() };
|
|
490
|
+
yield { type: 'step_end', stepNumber: steps, hasToolCalls: false, timestamp: Date.now() };
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
yield {
|
|
494
|
+
type: 'tool_calls',
|
|
495
|
+
toolCalls: response.toolCalls,
|
|
496
|
+
stepNumber: steps,
|
|
497
|
+
timestamp: Date.now(),
|
|
498
|
+
};
|
|
499
|
+
const toolResults = await this.executeToolCalls(response.toolCalls, abortSignal);
|
|
500
|
+
for (const result of toolResults) {
|
|
501
|
+
allToolCalls.push(result);
|
|
502
|
+
allToolResults.push({ toolName: result.name, result: result.result });
|
|
503
|
+
yield {
|
|
504
|
+
type: 'tool_result',
|
|
505
|
+
toolName: result.name,
|
|
506
|
+
...(result.result !== undefined && { result: result.result }),
|
|
507
|
+
...(result.error !== undefined && { error: result.error }),
|
|
508
|
+
stepNumber: steps,
|
|
509
|
+
timestamp: Date.now(),
|
|
510
|
+
};
|
|
511
|
+
if (result.error) {
|
|
512
|
+
messages.push({
|
|
513
|
+
role: 'tool',
|
|
514
|
+
content: JSON.stringify({ error: result.error }),
|
|
515
|
+
isError: true,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
messages.push({
|
|
520
|
+
role: 'tool',
|
|
521
|
+
content: JSON.stringify(result.result),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
yield { type: 'step_end', stepNumber: steps, hasToolCalls: true, timestamp: Date.now() };
|
|
526
|
+
messages.push({
|
|
527
|
+
role: 'assistant',
|
|
528
|
+
content: '',
|
|
529
|
+
tool_calls: response.toolCalls,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
if (steps >= maxSteps && stopReason === 'stop') {
|
|
533
|
+
stopReason = 'max_steps';
|
|
534
|
+
yield { type: 'max_steps', steps, timestamp: Date.now() };
|
|
535
|
+
if (strictMaxSteps)
|
|
536
|
+
throw new Error('Max steps exceeded');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (error.message === 'Aborted') {
|
|
541
|
+
stopReason = 'aborted';
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
if (error.message === 'Max steps exceeded') {
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
yield { type: 'error', error: error.message, timestamp: Date.now() };
|
|
548
|
+
stopReason = 'error';
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
yield { type: 'end', steps, stopReason, timestamp: Date.now() };
|
|
552
|
+
return {
|
|
553
|
+
text: finalText,
|
|
554
|
+
steps,
|
|
555
|
+
toolCalls: allToolCalls,
|
|
556
|
+
toolResults: allToolResults,
|
|
557
|
+
stopReason,
|
|
558
|
+
...(totalUsage !== undefined && { usage: totalUsage }),
|
|
559
|
+
messages,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// ============================================================================
|
|
564
|
+
// Tool Composition Patterns
|
|
565
|
+
// ============================================================================
|
|
566
|
+
/**
|
|
567
|
+
* Create a tool from a simple function
|
|
568
|
+
*/
|
|
569
|
+
export function createTool(config) {
|
|
570
|
+
return {
|
|
571
|
+
name: config.name,
|
|
572
|
+
description: config.description,
|
|
573
|
+
parameters: z.object(config.parameters),
|
|
574
|
+
execute: config.execute,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Compose multiple tools into a single toolset
|
|
579
|
+
*/
|
|
580
|
+
export function createToolset(...tools) {
|
|
581
|
+
return tools;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Create a tool that wraps another tool with middleware
|
|
585
|
+
*/
|
|
586
|
+
export function wrapTool(tool, middleware) {
|
|
587
|
+
return {
|
|
588
|
+
...tool,
|
|
589
|
+
execute: async (params) => {
|
|
590
|
+
try {
|
|
591
|
+
const modifiedParams = middleware.before ? await middleware.before(params) : params;
|
|
592
|
+
const result = await tool.execute(modifiedParams);
|
|
593
|
+
return middleware.after ? await middleware.after(result) : result;
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
if (middleware.onError) {
|
|
597
|
+
return middleware.onError(error);
|
|
598
|
+
}
|
|
599
|
+
throw error;
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Create a tool with caching support
|
|
606
|
+
*
|
|
607
|
+
* Features:
|
|
608
|
+
* - TTL-based expiration
|
|
609
|
+
* - Optional periodic cleanup of expired entries (prevents memory leaks)
|
|
610
|
+
* - Optional max size with LRU eviction
|
|
611
|
+
* - Manual cache control (clear, destroy)
|
|
612
|
+
*/
|
|
613
|
+
export function cachedTool(tool, options = {}) {
|
|
614
|
+
const { ttl = 60000, keyFn = JSON.stringify, cleanupIntervalMs = 0, maxSize = 0 } = options;
|
|
615
|
+
const cache = new Map();
|
|
616
|
+
let cleanupTimer = null;
|
|
617
|
+
let destroyed = false;
|
|
618
|
+
// Cleanup function to remove expired entries
|
|
619
|
+
const cleanupExpired = () => {
|
|
620
|
+
const now = Date.now();
|
|
621
|
+
for (const [key, entry] of cache) {
|
|
622
|
+
if (entry.expires <= now) {
|
|
623
|
+
cache.delete(key);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
// Start periodic cleanup if configured
|
|
628
|
+
if (cleanupIntervalMs > 0) {
|
|
629
|
+
cleanupTimer = setInterval(cleanupExpired, cleanupIntervalMs);
|
|
630
|
+
// Unref the timer so it doesn't keep the process alive (Node.js)
|
|
631
|
+
if (typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
|
|
632
|
+
cleanupTimer.unref();
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Evict oldest entries based on lastAccessed (LRU)
|
|
636
|
+
const evictOldest = () => {
|
|
637
|
+
if (maxSize <= 0 || cache.size < maxSize)
|
|
638
|
+
return;
|
|
639
|
+
// Find the entry with oldest lastAccessed
|
|
640
|
+
let oldestKey = null;
|
|
641
|
+
let oldestTime = Infinity;
|
|
642
|
+
for (const [key, entry] of cache) {
|
|
643
|
+
if (entry.lastAccessed < oldestTime) {
|
|
644
|
+
oldestTime = entry.lastAccessed;
|
|
645
|
+
oldestKey = key;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (oldestKey) {
|
|
649
|
+
cache.delete(oldestKey);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
const cachedToolInstance = {
|
|
653
|
+
...tool,
|
|
654
|
+
execute: async (params) => {
|
|
655
|
+
if (destroyed) {
|
|
656
|
+
// If destroyed, just execute without caching
|
|
657
|
+
return tool.execute(params);
|
|
658
|
+
}
|
|
659
|
+
const key = keyFn(params);
|
|
660
|
+
const cached = cache.get(key);
|
|
661
|
+
const now = Date.now();
|
|
662
|
+
if (cached && cached.expires > now) {
|
|
663
|
+
// Cache hit - update last accessed time for LRU
|
|
664
|
+
cached.lastAccessed = now;
|
|
665
|
+
return cached.value;
|
|
666
|
+
}
|
|
667
|
+
// Cache miss or expired - remove expired entry if present
|
|
668
|
+
if (cached) {
|
|
669
|
+
cache.delete(key);
|
|
670
|
+
}
|
|
671
|
+
const result = await tool.execute(params);
|
|
672
|
+
// Evict oldest if we're at max size
|
|
673
|
+
if (maxSize > 0 && cache.size >= maxSize) {
|
|
674
|
+
evictOldest();
|
|
675
|
+
}
|
|
676
|
+
cache.set(key, {
|
|
677
|
+
value: result,
|
|
678
|
+
expires: now + ttl,
|
|
679
|
+
lastAccessed: now,
|
|
680
|
+
});
|
|
681
|
+
return result;
|
|
682
|
+
},
|
|
683
|
+
cacheSize() {
|
|
684
|
+
return cache.size;
|
|
685
|
+
},
|
|
686
|
+
clearCache() {
|
|
687
|
+
cache.clear();
|
|
688
|
+
},
|
|
689
|
+
destroy() {
|
|
690
|
+
destroyed = true;
|
|
691
|
+
if (cleanupTimer !== null) {
|
|
692
|
+
clearInterval(cleanupTimer);
|
|
693
|
+
cleanupTimer = null;
|
|
694
|
+
}
|
|
695
|
+
cache.clear();
|
|
696
|
+
},
|
|
697
|
+
};
|
|
698
|
+
return cachedToolInstance;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Create a tool with rate limiting
|
|
702
|
+
*/
|
|
703
|
+
export function rateLimitedTool(tool, options) {
|
|
704
|
+
const calls = [];
|
|
705
|
+
const { maxCalls, windowMs } = options;
|
|
706
|
+
return {
|
|
707
|
+
...tool,
|
|
708
|
+
execute: async (params) => {
|
|
709
|
+
const now = Date.now();
|
|
710
|
+
// Remove expired calls
|
|
711
|
+
while (calls.length > 0 && calls[0] < now - windowMs) {
|
|
712
|
+
calls.shift();
|
|
713
|
+
}
|
|
714
|
+
if (calls.length >= maxCalls) {
|
|
715
|
+
throw new Error(`Rate limit exceeded: max ${maxCalls} calls per ${windowMs}ms`);
|
|
716
|
+
}
|
|
717
|
+
calls.push(now);
|
|
718
|
+
return tool.execute(params);
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Create a tool that times out after a specified duration
|
|
724
|
+
*/
|
|
725
|
+
export function timeoutTool(tool, timeoutMs) {
|
|
726
|
+
return {
|
|
727
|
+
...tool,
|
|
728
|
+
execute: async (params) => {
|
|
729
|
+
let timeoutId;
|
|
730
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
731
|
+
timeoutId = setTimeout(() => reject(new Error(`Tool '${tool.name}' timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
732
|
+
});
|
|
733
|
+
try {
|
|
734
|
+
return await Promise.race([tool.execute(params), timeoutPromise]);
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
if (timeoutId !== undefined) {
|
|
738
|
+
clearTimeout(timeoutId);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Create an agentic loop with sensible defaults
|
|
746
|
+
*
|
|
747
|
+
* @deprecated Phase C Week 2 — `createAgenticLoop` has zero production
|
|
748
|
+
* callers in primitives.org.ai (only `ai-primitives` umbrella re-export
|
|
749
|
+
* tests). Use AI SDK 6's `Agent` / `ToolLoopAgent` with
|
|
750
|
+
* `stopWhen: stepCountIs(N)` instead. Will be removed alongside
|
|
751
|
+
* `AgenticLoop` in the Phase C semver bump. See `bd show aip-ibid`.
|
|
752
|
+
*/
|
|
753
|
+
export function createAgenticLoop(options) {
|
|
754
|
+
return new AgenticLoop({
|
|
755
|
+
maxSteps: 10,
|
|
756
|
+
parallelExecution: true,
|
|
757
|
+
maxParallelCalls: 5,
|
|
758
|
+
continueOnError: true,
|
|
759
|
+
trackUsage: true,
|
|
760
|
+
...options,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
//# sourceMappingURL=tool-orchestration.js.map
|