@rudderjs/ai 0.0.1 → 0.0.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 (65) hide show
  1. package/README.md +89 -12
  2. package/boost/skills/ai-agents/SKILL.md +233 -0
  3. package/boost/skills/ai-tools/SKILL.md +244 -0
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +110 -0
  6. package/dist/agent.js.map +1 -1
  7. package/dist/commands/make-agent.d.ts +3 -0
  8. package/dist/commands/make-agent.d.ts.map +1 -0
  9. package/dist/commands/make-agent.js +23 -0
  10. package/dist/commands/make-agent.js.map +1 -0
  11. package/dist/facade.d.ts +28 -1
  12. package/dist/facade.d.ts.map +1 -1
  13. package/dist/facade.js +25 -0
  14. package/dist/facade.js.map +1 -1
  15. package/dist/fake.d.ts +31 -1
  16. package/dist/fake.d.ts.map +1 -1
  17. package/dist/fake.js +98 -0
  18. package/dist/fake.js.map +1 -1
  19. package/dist/files.d.ts +27 -0
  20. package/dist/files.d.ts.map +1 -0
  21. package/dist/files.js +44 -0
  22. package/dist/files.js.map +1 -0
  23. package/dist/index.d.ts +6 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/observers.d.ts +84 -0
  28. package/dist/observers.d.ts.map +1 -0
  29. package/dist/observers.js +40 -0
  30. package/dist/observers.js.map +1 -0
  31. package/dist/provider.d.ts +8 -6
  32. package/dist/provider.d.ts.map +1 -1
  33. package/dist/provider.js +86 -81
  34. package/dist/provider.js.map +1 -1
  35. package/dist/providers/anthropic.d.ts +2 -1
  36. package/dist/providers/anthropic.d.ts.map +1 -1
  37. package/dist/providers/anthropic.js +63 -0
  38. package/dist/providers/anthropic.js.map +1 -1
  39. package/dist/providers/cohere.d.ts +13 -0
  40. package/dist/providers/cohere.d.ts.map +1 -0
  41. package/dist/providers/cohere.js +87 -0
  42. package/dist/providers/cohere.js.map +1 -0
  43. package/dist/providers/google.d.ts +2 -1
  44. package/dist/providers/google.d.ts.map +1 -1
  45. package/dist/providers/google.js +53 -0
  46. package/dist/providers/google.js.map +1 -1
  47. package/dist/providers/jina.d.ts +13 -0
  48. package/dist/providers/jina.d.ts.map +1 -0
  49. package/dist/providers/jina.js +90 -0
  50. package/dist/providers/jina.js.map +1 -0
  51. package/dist/providers/openai.d.ts +2 -1
  52. package/dist/providers/openai.d.ts.map +1 -1
  53. package/dist/providers/openai.js +62 -0
  54. package/dist/providers/openai.js.map +1 -1
  55. package/dist/registry.d.ts +5 -1
  56. package/dist/registry.d.ts.map +1 -1
  57. package/dist/registry.js +19 -0
  58. package/dist/registry.js.map +1 -1
  59. package/dist/rerank.d.ts +20 -0
  60. package/dist/rerank.d.ts.map +1 -0
  61. package/dist/rerank.js +40 -0
  62. package/dist/rerank.js.map +1 -0
  63. package/dist/types.d.ts +46 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/package.json +18 -4
package/README.md CHANGED
@@ -14,7 +14,8 @@ Install the provider SDK(s) you need:
14
14
  pnpm add @anthropic-ai/sdk # Anthropic (Claude)
15
15
  pnpm add openai # OpenAI (GPT)
16
16
  pnpm add @google/genai # Google (Gemini)
17
- # Ollama no extra package needed (OpenAI-compatible)
17
+ pnpm add cohere-ai # Cohere (reranking + embeddings)
18
+ # Ollama, Jina — no extra package needed
18
19
  ```
19
20
 
20
21
  ## Setup
@@ -28,6 +29,8 @@ export default {
28
29
  openai: { driver: 'openai', apiKey: process.env.OPENAI_API_KEY! },
29
30
  google: { driver: 'google', apiKey: process.env.GOOGLE_API_KEY! },
30
31
  ollama: { driver: 'ollama', baseUrl: 'http://localhost:11434' },
32
+ cohere: { driver: 'cohere', apiKey: process.env.COHERE_API_KEY! },
33
+ jina: { driver: 'jina', apiKey: process.env.JINA_API_KEY! },
31
34
  },
32
35
  }
33
36
 
@@ -332,6 +335,55 @@ const agent = AI.agent({
332
335
  })
333
336
  ```
334
337
 
338
+ ### Reranking
339
+
340
+ Reorder documents by relevance to a query — useful for RAG pipelines:
341
+
342
+ ```ts
343
+ import { AI } from '@rudderjs/ai'
344
+
345
+ // One-shot
346
+ const result = await AI.rerank('search query', documents, {
347
+ model: 'cohere/rerank-v3.5',
348
+ topK: 5,
349
+ })
350
+ // result.results → [{ index, relevanceScore, document }, ...]
351
+
352
+ // Fluent builder
353
+ const result = await AI.rerank('how to deploy', docs)
354
+ .model('jina/jina-reranker-v2-base-multilingual')
355
+ .topK(10)
356
+ .rank()
357
+ ```
358
+
359
+ Supported providers: **Cohere** (`cohere-ai` SDK) and **Jina** (direct HTTP, no SDK).
360
+
361
+ ### File Management
362
+
363
+ Upload, list, and delete files on provider platforms — needed for large document context and assistant APIs:
364
+
365
+ ```ts
366
+ import { AI } from '@rudderjs/ai'
367
+
368
+ const files = AI.files('openai')
369
+
370
+ // Upload
371
+ const uploaded = await files.upload('./report.pdf', { purpose: 'assistants' })
372
+ // uploaded → { id, filename, bytes, purpose }
373
+
374
+ // List
375
+ const { files: allFiles } = await files.list()
376
+
377
+ // Delete
378
+ await files.delete(uploaded.id)
379
+
380
+ // Retrieve content (OpenAI, Anthropic)
381
+ const content = await files.retrieve(uploaded.id)
382
+ // content → { data: Buffer, mimeType }
383
+ ```
384
+
385
+ Supported providers: **OpenAI** (full CRUD + retrieve), **Anthropic** (full CRUD + retrieve), **Google** (upload, list, delete — no retrieve).
386
+
335
387
  ### Embeddings
336
388
 
337
389
  ```ts
@@ -439,19 +491,43 @@ fake.assertPrompted(input => input.includes('Hello'))
439
491
  fake.restore()
440
492
  ```
441
493
 
494
+ Fakes cover every modality:
495
+
496
+ ```ts
497
+ fake.respondWith('text') // text generation
498
+ fake.respondWithImage('base64...') // image generation
499
+ fake.respondWithAudio(Buffer.from('')) // TTS
500
+ fake.respondWithTranscription('text') // STT
501
+ fake.respondWithEmbedding([[0.1, 0.2]]) // embeddings
502
+ fake.respondWithRanking([ // reranking
503
+ { index: 0, relevanceScore: 0.95, document: 'most relevant' },
504
+ ])
505
+ fake.respondWithFileUpload({ // file upload
506
+ id: 'file-123', filename: 'report.pdf', bytes: 1024,
507
+ })
508
+
509
+ // Assertions
510
+ fake.assertPrompted() fake.assertImageGenerated()
511
+ fake.assertAudioGenerated() fake.assertTranscribed()
512
+ fake.assertEmbedded() fake.assertReranked()
513
+ fake.assertFileUploaded()
514
+ ```
515
+
442
516
  ## Providers
443
517
 
444
- | Provider | SDK | Model String | Embeddings | Images | TTS/STT |
445
- |---|---|---|---|---|---|
446
- | Anthropic | `@anthropic-ai/sdk` | `anthropic/claude-sonnet-4-5` | | | |
447
- | OpenAI | `openai` | `openai/gpt-4o` | ✓ | ✓ | ✓ |
448
- | Google | `@google/genai` | `google/gemini-2.5-pro` | ✓ | ✓ | |
449
- | Ollama | *(none)* | `ollama/llama3` | | | |
450
- | Groq | *(none)* | `groq/llama-3.3-70b` | | | |
451
- | DeepSeek | *(none)* | `deepseek/deepseek-chat` | | | |
452
- | xAI | *(none)* | `xai/grok-3` | | | |
453
- | Mistral | *(none)* | `mistral/mistral-large` | ✓ | | |
454
- | Azure OpenAI | `openai` | `azure/gpt-4o` | | | |
518
+ | Provider | SDK | Model String | Text | Embeddings | Images | TTS/STT | Reranking | Files |
519
+ |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
520
+ | Anthropic | `@anthropic-ai/sdk` | `anthropic/claude-sonnet-4-5` | | | | | | ✓ |
521
+ | OpenAI | `openai` | `openai/gpt-4o` | ✓ | ✓ | ✓ | ✓ | | ✓ |
522
+ | Google | `@google/genai` | `google/gemini-2.5-pro` | ✓ | ✓ | | | | ✓ |
523
+ | Cohere | `cohere-ai` | `cohere/rerank-v3.5` | | | | | ✓ | |
524
+ | Jina | *(none)* | `jina/jina-reranker-v2-base-multilingual` | | | | | ✓ | |
525
+ | Ollama | *(none)* | `ollama/llama3` | | | | | | |
526
+ | Groq | *(none)* | `groq/llama-3.3-70b` | | | | | | |
527
+ | DeepSeek | *(none)* | `deepseek/deepseek-chat` | ✓ | | | | | |
528
+ | xAI | *(none)* | `xai/grok-3` | | | | | | |
529
+ | Mistral | *(none)* | `mistral/mistral-large` | ✓ | ✓ | | | | |
530
+ | Azure OpenAI | `openai` | `azure/gpt-4o` | ✓ | | | | | |
455
531
 
456
532
  ## Notes
457
533
 
@@ -459,3 +535,4 @@ fake.restore()
459
535
  - `exactOptionalPropertyTypes` compatible
460
536
  - All adapters lazy-load their SDK on first use
461
537
  - Ollama, Groq, DeepSeek, xAI, Mistral reuse the OpenAI adapter (OpenAI-compatible API)
538
+ - Cohere requires `cohere-ai` SDK; Jina uses direct HTTP (no SDK needed)
@@ -0,0 +1,233 @@
1
+ ---
2
+ name: ai-agents
3
+ description: Building AI agents with tools, streaming, conversation memory, approval flows, and middleware in RudderJS
4
+ ---
5
+
6
+ # AI Agents
7
+
8
+ ## When to use this skill
9
+
10
+ Load this skill when you need to build an AI agent, run prompts with tool loops, stream responses, persist conversations, use approval gates, or queue agent work for background execution.
11
+
12
+ ## Key concepts
13
+
14
+ - **Agent base class**: Extend `Agent` and implement `instructions()`. Optionally override `model()`, `tools()`, `maxSteps()`, `stopWhen()`, `temperature()`, `middleware()`.
15
+ - **Anonymous agents**: Use the `agent()` function for inline, one-off agents without a class.
16
+ - **Tool loop**: The agent runs a loop: prompt model -> execute tool calls -> feed results back -> repeat until stop condition.
17
+ - **Streaming**: `agent.stream()` returns `{ stream: AsyncIterable<StreamChunk>, response: Promise<AgentResponse> }`.
18
+ - **Conversations**: `agent.forUser(id).prompt()` or `agent.continue(conversationId).prompt()` for persistent memory.
19
+ - **Provider/model string**: Format is `'provider/model'` (e.g. `'anthropic/claude-sonnet-4-5'`, `'openai/gpt-4o'`).
20
+ - **Finish reasons**: `'stop'`, `'tool_calls'`, `'length'`, `'client_tool_calls'`, `'tool_approval_required'`.
21
+
22
+ ## Step-by-step
23
+
24
+ ### 1. Create an agent class
25
+
26
+ ```ts
27
+ // app/Agents/ResearchAgent.ts
28
+ import { Agent } from '@rudderjs/ai'
29
+ import type { HasTools, AnyTool } from '@rudderjs/ai'
30
+
31
+ export class ResearchAgent extends Agent implements HasTools {
32
+ instructions(): string {
33
+ return `You are a research assistant. Use the search tool to find
34
+ information and summarize your findings clearly.`
35
+ }
36
+
37
+ model(): string {
38
+ return 'anthropic/claude-sonnet-4-5'
39
+ }
40
+
41
+ tools(): AnyTool[] {
42
+ return [searchTool, summarizeTool]
43
+ }
44
+
45
+ maxSteps(): number {
46
+ return 10
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### 2. Run a prompt (non-streaming)
52
+
53
+ ```ts
54
+ const agent = new ResearchAgent()
55
+
56
+ const response = await agent.prompt('What is RudderJS?')
57
+ console.log(response.text) // final text output
58
+ console.log(response.steps) // array of AgentStep
59
+ console.log(response.usage) // { promptTokens, completionTokens, totalTokens }
60
+ ```
61
+
62
+ ### 3. Stream a response
63
+
64
+ ```ts
65
+ const { stream, response } = agent.stream('Explain TypeScript decorators')
66
+
67
+ for await (const chunk of stream) {
68
+ switch (chunk.type) {
69
+ case 'text-delta':
70
+ process.stdout.write(chunk.text ?? '')
71
+ break
72
+ case 'tool-call':
73
+ console.log(`Calling tool: ${chunk.toolCall?.name}`)
74
+ break
75
+ case 'tool-result':
76
+ console.log(`Tool result:`, chunk.result)
77
+ break
78
+ case 'tool-update':
79
+ console.log(`Progress:`, chunk.update)
80
+ break
81
+ case 'finish':
82
+ console.log(`Done: ${chunk.finishReason}`)
83
+ break
84
+ }
85
+ }
86
+
87
+ const finalResponse = await response
88
+ ```
89
+
90
+ ### 4. Use anonymous agents (inline)
91
+
92
+ ```ts
93
+ import { agent } from '@rudderjs/ai'
94
+
95
+ // Simple string instructions
96
+ const response = await agent('You are a helpful assistant.').prompt('Hello')
97
+
98
+ // With tools and model
99
+ const response = await agent({
100
+ instructions: 'You are a search assistant.',
101
+ tools: [searchTool],
102
+ model: 'anthropic/claude-sonnet-4-5',
103
+ }).prompt('Find users named John')
104
+ ```
105
+
106
+ ### 5. Conversation persistence
107
+
108
+ ```ts
109
+ const myAgent = new ResearchAgent()
110
+
111
+ // Start a new conversation for a user
112
+ const response1 = await myAgent.forUser('user-123').prompt('What is TypeScript?')
113
+ const convId = response1.conversationId!
114
+
115
+ // Continue the same conversation
116
+ const response2 = await myAgent.continue(convId).prompt('Tell me more about generics')
117
+ // The agent sees the full conversation history
118
+
119
+ // Streaming with conversations
120
+ const { stream, response } = myAgent.forUser('user-123').stream('Explain async/await')
121
+ ```
122
+
123
+ A `ConversationStore` must be registered. The built-in `MemoryConversationStore` works for dev; implement the `ConversationStore` interface for production (database-backed).
124
+
125
+ ### 6. Stop conditions
126
+
127
+ ```ts
128
+ import { Agent, stepCountIs, hasToolCall } from '@rudderjs/ai'
129
+
130
+ class MyAgent extends Agent {
131
+ instructions() { return 'You are helpful.' }
132
+
133
+ stopWhen() {
134
+ return [
135
+ stepCountIs(5), // stop after 5 iterations
136
+ hasToolCall('final_answer'), // stop when this tool is called
137
+ ]
138
+ // Multiple conditions use OR logic -- stops when any is true
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### 7. Per-step control (prepareStep)
144
+
145
+ ```ts
146
+ class AdaptiveAgent extends Agent {
147
+ instructions() { return 'You are helpful.' }
148
+
149
+ prepareStep(ctx: { stepNumber: number; steps: AgentStep[]; messages: AiMessage[] }) {
150
+ if (ctx.stepNumber > 3) {
151
+ return { model: 'anthropic/claude-haiku-3' } // cheaper model for later steps
152
+ }
153
+ return {}
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### 8. Middleware
159
+
160
+ ```ts
161
+ import type { AiMiddleware } from '@rudderjs/ai'
162
+
163
+ const loggingMiddleware: AiMiddleware = {
164
+ name: 'logging',
165
+ onStart(ctx) { console.log(`Agent started, model: ${ctx.model}`) },
166
+ onChunk(ctx, chunk) {
167
+ if (chunk.type === 'text-delta') process.stdout.write(chunk.text ?? '')
168
+ return chunk // return null to suppress the chunk
169
+ },
170
+ onBeforeToolCall(ctx, toolName, args) {
171
+ console.log(`Calling ${toolName}`, args)
172
+ // Return { type: 'skip', result: 'mocked' } to skip execution
173
+ // Return { type: 'abort', reason: 'blocked' } to abort the loop
174
+ },
175
+ onAfterToolCall(ctx, toolName, args, result) {
176
+ console.log(`${toolName} returned`, result)
177
+ },
178
+ onUsage(ctx, usage) {
179
+ console.log(`Tokens: ${usage.totalTokens}`)
180
+ },
181
+ onError(ctx, error) {
182
+ console.error('Agent error:', error)
183
+ },
184
+ }
185
+
186
+ class MyAgent extends Agent implements HasMiddleware {
187
+ instructions() { return 'You are helpful.' }
188
+ middleware() { return [loggingMiddleware] }
189
+ }
190
+ ```
191
+
192
+ ### 9. Queue for background execution
193
+
194
+ ```ts
195
+ const myAgent = new ResearchAgent()
196
+
197
+ // Queue for async processing (requires @rudderjs/queue)
198
+ myAgent.queue('Analyze this dataset').dispatch()
199
+ ```
200
+
201
+ ### 10. Failover providers
202
+
203
+ ```ts
204
+ class ResilientAgent extends Agent {
205
+ instructions() { return 'You are helpful.' }
206
+ model() { return 'anthropic/claude-sonnet-4-5' }
207
+ failover() { return ['openai/gpt-4o', 'google/gemini-2.0-flash'] }
208
+ }
209
+ ```
210
+
211
+ ### 11. Attachments (images/documents)
212
+
213
+ ```ts
214
+ const response = await agent('Describe this image.').prompt('What do you see?', {
215
+ attachments: [
216
+ { type: 'image', data: base64String, mimeType: 'image/png' },
217
+ { type: 'document', data: pdfBase64, mimeType: 'application/pdf', name: 'report.pdf' },
218
+ ],
219
+ })
220
+ ```
221
+
222
+ ## Examples
223
+
224
+ See `playground/app/Agents/ResearchAgent.ts` for a working agent class.
225
+
226
+ ## Common pitfalls
227
+
228
+ - **Provider SDK not installed**: Each provider's SDK is an optional peer dependency. Install only what you use: `@anthropic-ai/sdk`, `openai`, `@google/genai`.
229
+ - **No default model**: If `model()` returns `undefined`, the agent uses the registry default from `config/ai.ts`. Make sure one is configured.
230
+ - **ConversationStore missing**: `forUser()` / `continue()` throw if no `ConversationStore` is registered. Register one via `setConversationStore()` or through the AI service provider.
231
+ - **maxSteps exhaustion**: Default is 20 iterations. If the agent hits `maxSteps`, it stops with whatever text it has. Override `maxSteps()` for agents that need more iterations.
232
+ - **Streaming vs non-streaming tool updates**: `yield` from an `async function*` tool execute emits `tool-update` chunks during streaming. In non-streaming `prompt()`, yields are silently drained.
233
+ - **Client tools**: Tools without an `execute` function are client tools. The loop pauses with `finishReason: 'client_tool_calls'` and returns `pendingClientToolCalls` for browser-side execution.
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: ai-tools
3
+ description: Defining server and client tools with Zod schemas, approval gates, streaming yields, and modelOutput for RudderJS AI agents
4
+ ---
5
+
6
+ # AI Tools
7
+
8
+ ## When to use this skill
9
+
10
+ Load this skill when you need to define tools for AI agents -- server-side executors, client-side browser tools, streaming generator tools, approval gates, or tools with custom model output formatting.
11
+
12
+ ## Key concepts
13
+
14
+ - **toolDefinition()**: Builder function that creates a typed tool from a Zod input schema. Call `.server()` to attach a handler, or leave as-is for a client tool.
15
+ - **Server tools**: Have an `execute` function that runs on the server. Can be a regular async function or an `async function*` generator.
16
+ - **Client tools**: No `execute` -- the agent loop pauses and returns pending tool calls for browser-side execution.
17
+ - **Approval gates**: `needsApproval: true` (or a predicate function) pauses the loop with `tool_approval_required` finish reason.
18
+ - **modelOutput()**: Transform the tool's structured result into a shorter string for the model's context, while the UI still gets the full result.
19
+ - **Tool updates (streaming)**: Generator tools can `yield` progress payloads that surface as `tool-update` stream chunks.
20
+
21
+ ## Step-by-step
22
+
23
+ ### 1. Basic server tool
24
+
25
+ ```ts
26
+ import { toolDefinition } from '@rudderjs/ai'
27
+ import { z } from 'zod'
28
+
29
+ const weatherTool = toolDefinition({
30
+ name: 'get_weather',
31
+ description: 'Get current weather for a location',
32
+ inputSchema: z.object({
33
+ location: z.string().describe('City name'),
34
+ units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
35
+ }),
36
+ }).server(async ({ location, units }) => {
37
+ const data = await fetchWeather(location, units)
38
+ return { temp: data.temperature, conditions: data.conditions, unit: units }
39
+ })
40
+ ```
41
+
42
+ ### 2. Client tool (browser-side execution)
43
+
44
+ ```ts
45
+ // No .server() call -- this is a client tool
46
+ const readClipboardTool = toolDefinition({
47
+ name: 'read_clipboard',
48
+ description: 'Read the contents of the user clipboard',
49
+ inputSchema: z.object({}),
50
+ })
51
+
52
+ // When the agent calls this tool, the loop pauses with:
53
+ // finishReason: 'client_tool_calls'
54
+ // pendingClientToolCalls: [{ id, name: 'read_clipboard', arguments: {} }]
55
+ // The caller executes it browser-side and resumes with tool results.
56
+ ```
57
+
58
+ ### 3. Tool with approval gate
59
+
60
+ ```ts
61
+ const deleteUserTool = toolDefinition({
62
+ name: 'delete_user',
63
+ description: 'Permanently delete a user account',
64
+ inputSchema: z.object({ userId: z.string() }),
65
+ needsApproval: true, // always requires approval
66
+ }).server(async ({ userId }) => {
67
+ await User.forceDelete(userId)
68
+ return { deleted: true }
69
+ })
70
+
71
+ // Conditional approval
72
+ const sendEmailTool = toolDefinition({
73
+ name: 'send_email',
74
+ description: 'Send an email to a user',
75
+ inputSchema: z.object({
76
+ to: z.string(),
77
+ subject: z.string(),
78
+ body: z.string(),
79
+ }),
80
+ needsApproval: (input) => input.to.endsWith('@external.com'),
81
+ }).server(async (input) => {
82
+ await sendEmail(input)
83
+ return { sent: true }
84
+ })
85
+ ```
86
+
87
+ When approval is required, the loop stops with:
88
+ - `finishReason: 'tool_approval_required'`
89
+ - `pendingApprovalToolCall: { toolCall, isClientTool: false }`
90
+
91
+ Resume by passing `approvedToolCallIds` or `rejectedToolCallIds` in the next prompt options.
92
+
93
+ ### 4. Streaming tool with progress yields
94
+
95
+ ```ts
96
+ const analyzeDataTool = toolDefinition({
97
+ name: 'analyze_data',
98
+ description: 'Analyze a dataset and return insights',
99
+ inputSchema: z.object({ datasetId: z.string() }),
100
+ }).server(async function* ({ datasetId }) {
101
+ const dataset = await loadDataset(datasetId)
102
+
103
+ yield { progress: 25, message: 'Loading data...' }
104
+
105
+ const cleaned = cleanData(dataset)
106
+ yield { progress: 50, message: 'Cleaning data...' }
107
+
108
+ const analysis = runAnalysis(cleaned)
109
+ yield { progress: 75, message: 'Running analysis...' }
110
+
111
+ const insights = summarize(analysis)
112
+ yield { progress: 100, message: 'Complete' }
113
+
114
+ return { insights, recordCount: dataset.length }
115
+ // Each yield surfaces as a 'tool-update' StreamChunk
116
+ // The return value is the final 'tool-result'
117
+ })
118
+ ```
119
+
120
+ ### 5. modelOutput() -- control what the model sees
121
+
122
+ ```ts
123
+ const searchTool = toolDefinition({
124
+ name: 'search_documents',
125
+ description: 'Search the document database',
126
+ inputSchema: z.object({ query: z.string() }),
127
+ }).server(async ({ query }) => {
128
+ const results = await searchDb(query)
129
+ return {
130
+ results, // full structured data for the UI
131
+ totalCount: results.length,
132
+ metadata: { /* ... */ },
133
+ }
134
+ }).modelOutput((result) => {
135
+ // The MODEL only sees this condensed string on its next step
136
+ // The UI still receives the full structured result above
137
+ return `Found ${result.totalCount} results: ${result.results.map(r => r.title).join(', ')}`
138
+ })
139
+ ```
140
+
141
+ ### 6. Dynamic tools (runtime-defined schemas)
142
+
143
+ ```ts
144
+ import { dynamicTool } from '@rudderjs/ai'
145
+
146
+ // When the schema isn't known at compile time
147
+ const tool = dynamicTool({
148
+ name: agentDef.slug,
149
+ description: agentDef.description,
150
+ inputSchema: z.object({}),
151
+ }).server(async () => {
152
+ return await agentDef.run()
153
+ })
154
+ ```
155
+
156
+ ### 7. Tool with ToolCallContext
157
+
158
+ ```ts
159
+ const myTool = toolDefinition({
160
+ name: 'my_tool',
161
+ description: 'A tool that needs its call ID',
162
+ inputSchema: z.object({ data: z.string() }),
163
+ }).server(async (input, ctx) => {
164
+ // ctx.toolCallId is the unique ID the model assigned to this call
165
+ console.log(`Tool call ID: ${ctx?.toolCallId}`)
166
+ return { processed: true }
167
+ })
168
+ ```
169
+
170
+ ### 8. Lazy tools (not advertised until needed)
171
+
172
+ ```ts
173
+ const secretTool = toolDefinition({
174
+ name: 'admin_panel',
175
+ description: 'Access admin functions',
176
+ inputSchema: z.object({ action: z.string() }),
177
+ lazy: true, // not included in the tool list sent to the model
178
+ }).server(async ({ action }) => {
179
+ // Only callable if the model explicitly names it
180
+ return { result: await adminAction(action) }
181
+ })
182
+ ```
183
+
184
+ ### 9. Pause for client tools (from inside a server tool)
185
+
186
+ ```ts
187
+ import { pauseForClientTools } from '@rudderjs/ai'
188
+
189
+ const runSubAgentTool = toolDefinition({
190
+ name: 'run_sub_agent',
191
+ description: 'Run a sub-agent that may need browser tools',
192
+ inputSchema: z.object({ task: z.string() }),
193
+ }).server(async function* ({ task }, ctx) {
194
+ const subResponse = await runSubAgent(task)
195
+
196
+ if (subResponse.pendingClientToolCalls?.length) {
197
+ // Pause the parent loop -- surface client tool calls to the browser
198
+ yield pauseForClientTools(subResponse.pendingClientToolCalls, subResponse.resumeId)
199
+ return undefined as never // unreachable after pause
200
+ }
201
+
202
+ return subResponse.text
203
+ })
204
+ ```
205
+
206
+ ### 10. Using tools with an agent
207
+
208
+ ```ts
209
+ import { Agent } from '@rudderjs/ai'
210
+ import type { HasTools, AnyTool } from '@rudderjs/ai'
211
+
212
+ class MyAgent extends Agent implements HasTools {
213
+ instructions() { return 'You are a helpful assistant with access to tools.' }
214
+
215
+ tools(): AnyTool[] {
216
+ return [
217
+ weatherTool,
218
+ searchTool,
219
+ analyzeDataTool,
220
+ deleteUserTool,
221
+ ]
222
+ }
223
+ }
224
+
225
+ // Or with the anonymous agent
226
+ const response = await agent({
227
+ instructions: 'You are helpful.',
228
+ tools: [weatherTool, searchTool],
229
+ }).prompt('What is the weather in Paris?')
230
+ ```
231
+
232
+ ## Examples
233
+
234
+ Tools are typically defined in `app/Tools/` or co-located with the agent that uses them. See `packages/ai/src/tool.ts` for the full builder API.
235
+
236
+ ## Common pitfalls
237
+
238
+ - **Zod schemas required**: Tool input schemas must be Zod objects. They are converted to JSON Schema for each provider automatically.
239
+ - **Generator vs async function**: Use `async function*` only when you need streaming progress yields. For simple tools, use a regular `async` function.
240
+ - **modelOutput is optional**: Only use `.modelOutput()` when the tool returns large structured data that would waste model context. The default behavior is `JSON.stringify` of the result.
241
+ - **Approval flow is two-step**: When a tool needs approval, the loop stops. You must resume with `approvedToolCallIds` or `rejectedToolCallIds` in the next `prompt()` call's options.
242
+ - **Client tool placeholder mode**: By default, client tools without `execute` get a placeholder result and the loop continues. Pass `toolCallStreamingMode: 'stop-on-client-tool'` to pause instead.
243
+ - **exactOptionalPropertyTypes**: If your tsconfig has this enabled, do not pass `undefined` for optional tool parameters -- omit the key entirely.
244
+ - **Tool name conventions**: Use `snake_case` for tool names (e.g. `get_weather`, `search_documents`). This matches what AI models expect.
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAWpD,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EAEZ,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,OAAO,EAEP,iBAAiB,EAEjB,aAAa,EACb,QAAQ,EAER,iBAAiB,EAEjB,aAAa,EAQd,MAAM,YAAY,CAAA;AAInB,yBAAyB;AACzB,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAK3D;AAID,8BAAsB,KAAK;IACzB,yCAAyC;IACzC,QAAQ,CAAC,YAAY,IAAI,MAAM;IAE/B,uFAAuF;IACvF,KAAK,IAAI,MAAM,GAAG,SAAS;IAE3B,sCAAsC;IACtC,QAAQ,IAAI,MAAM,EAAE;IAEpB,yDAAyD;IACzD,QAAQ,IAAI,MAAM;IAElB,uEAAuE;IACvE,WAAW,CAAC,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAErI,sDAAsD;IACtD,QAAQ,IAAI,aAAa,GAAG,aAAa,EAAE;IAI3C,wBAAwB;IACxB,WAAW,IAAI,MAAM,GAAG,SAAS;IAEjC,8BAA8B;IAC9B,SAAS,IAAI,MAAM,GAAG,SAAS;IAE/B,kDAAkD;IAC5C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjF,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;IAIxE,gDAAgD;IAChD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;IAIvE,sDAAsD;IACtD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAIzC,wCAAwC;IACxC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB;CAGnD;AAID;;;GAGG;AACH,qBAAa,gBAAgB;IAIf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,eAAe,CAAoB;gBAEd,KAAK,EAAE,KAAK;IAEzC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK7B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAKhC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAgCjF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;CA0DzE;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CACnB,qBAAqB,EAAE,MAAM,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,UAAU,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;CACxC,GACA,KAAK,GAAG,QAAQ,GAAG,aAAa,CAKlC;AAQD,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEnE"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAYpD,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EAEZ,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,OAAO,EAEP,iBAAiB,EAEjB,aAAa,EACb,QAAQ,EAER,iBAAiB,EAEjB,aAAa,EAQd,MAAM,YAAY,CAAA;AA2BnB,yBAAyB;AACzB,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,aAAa,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAK3D;AAID,8BAAsB,KAAK;IACzB,yCAAyC;IACzC,QAAQ,CAAC,YAAY,IAAI,MAAM;IAE/B,uFAAuF;IACvF,KAAK,IAAI,MAAM,GAAG,SAAS;IAE3B,sCAAsC;IACtC,QAAQ,IAAI,MAAM,EAAE;IAEpB,yDAAyD;IACzD,QAAQ,IAAI,MAAM;IAElB,uEAAuE;IACvE,WAAW,CAAC,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAErI,sDAAsD;IACtD,QAAQ,IAAI,aAAa,GAAG,aAAa,EAAE;IAI3C,wBAAwB;IACxB,WAAW,IAAI,MAAM,GAAG,SAAS;IAEjC,8BAA8B;IAC9B,SAAS,IAAI,MAAM,GAAG,SAAS;IAE/B,kDAAkD;IAC5C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjF,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;IAIxE,gDAAgD;IAChD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;IAIvE,sDAAsD;IACtD,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB;IAIzC,wCAAwC;IACxC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB;CAGnD;AAID;;;GAGG;AACH,qBAAa,gBAAgB;IAIf,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,eAAe,CAAoB;gBAEd,KAAK,EAAE,KAAK;IAEzC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK7B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAKhC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAgCjF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,mBAAmB;CA0DzE;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CACnB,qBAAqB,EAAE,MAAM,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC1B,UAAU,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAA;CACxC,GACA,KAAK,GAAG,QAAQ,GAAG,aAAa,CAKlC;AAQD,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEnE"}