@aeye/core 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,8 @@ Core primitives for building AI agents, tools, and prompts with TypeScript. Prov
19
19
  npm install @aeye/core zod handlebars
20
20
  ```
21
21
 
22
+ > **Note:** `@aeye/core` defines the primitives — you need an executor/streamer to run prompts. These are provided by `@aeye/ai` via `ai.buildCoreContext()`, or you can supply your own `execute` / `stream` functions.
23
+
22
24
  ## Core Concepts
23
25
 
24
26
  ### Components
@@ -46,29 +48,29 @@ const summarizer = new Prompt({
46
48
  name: 'summarize',
47
49
  description: 'Summarizes text concisely',
48
50
  content: 'Summarize the following text:\n\n{{text}}',
49
-
50
- // Transform input
51
+
52
+ // Transform raw input into template variables
51
53
  input: (input: { text: string }) => ({ text: input.text }),
52
-
53
- // Define output schema
54
+
55
+ // Define output schema (structured JSON)
54
56
  schema: z.object({
55
57
  summary: z.string().describe('A concise summary'),
56
- keyPoints: z.array(z.string()).describe('Main points')
57
- })
58
+ keyPoints: z.array(z.string()).describe('Main points'),
59
+ }),
58
60
  });
59
61
 
60
- // Execute with a context that has an executor
62
+ // Execute — ctx must provide execute or stream (from @aeye/ai or custom)
61
63
  const result = await summarizer.get(
62
64
  'result',
63
65
  { text: 'Long article text...' },
64
66
  {
65
- execute: yourAIExecutor, // Executor function from a provider
66
- messages: []
67
+ execute: yourAIExecutor, // (request, ctx, metadata?, signal?) => Promise<Response>
68
+ messages: [],
67
69
  }
68
70
  );
69
71
 
70
- console.log(result.summary);
71
- console.log(result.keyPoints);
72
+ console.log(result?.summary);
73
+ console.log(result?.keyPoints);
72
74
  ```
73
75
 
74
76
  ### Creating Tools
@@ -77,69 +79,73 @@ console.log(result.keyPoints);
77
79
  import { Tool } from '@aeye/core';
78
80
  import z from 'zod';
79
81
 
80
- const weatherTool = new Tool({
82
+ const getWeather = new Tool({
81
83
  name: 'getWeather',
82
84
  description: 'Get current weather for a location',
83
- instructions: 'Use this tool to get weather data',
84
-
85
+ instructions: 'Use this tool to get weather data for {{location}}.',
86
+
85
87
  schema: z.object({
86
88
  location: z.string().describe('City name or coordinates'),
87
- units: z.enum(['celsius', 'fahrenheit']).default('celsius')
89
+ units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
88
90
  }),
89
-
90
- call: async (input, refs, ctx) => {
91
+
92
+ call: async (input, _refs, _ctx) => {
91
93
  const response = await fetch(
92
- `https://api.weather.com/v1/${input.location}`
94
+ `https://api.weather.com/v1/${encodeURIComponent(input.location)}`
93
95
  );
94
96
  const data = await response.json();
95
-
96
97
  return {
97
98
  temperature: data.temp,
98
99
  condition: data.condition,
99
- humidity: data.humidity
100
+ humidity: data.humidity,
100
101
  };
101
- }
102
+ },
102
103
  });
103
104
  ```
104
105
 
105
106
  ### Prompts with Tools
106
107
 
107
108
  ```typescript
109
+ import { Prompt } from '@aeye/core';
110
+ import z from 'zod';
111
+
108
112
  const travelAdvisor = new Prompt({
109
113
  name: 'travelAdvisor',
110
114
  description: 'Provides travel advice based on weather',
111
115
  content: `You are a travel advisor. Help plan a trip to {{destination}}.
112
-
113
- Use the weather tool to check current conditions, then provide:
116
+
117
+ Use the getWeather tool to check current conditions, then provide:
114
118
  - What to pack
115
- - Activities to do
119
+ - Recommended activities
116
120
  - Best times to visit`,
117
-
118
- input: (input: { destination: string }) => ({
119
- destination: input.destination
121
+
122
+ input: (input: { destination: string }) => ({
123
+ destination: input.destination,
120
124
  }),
121
-
122
- tools: [weatherTool],
123
-
125
+
126
+ tools: [getWeather],
127
+
124
128
  schema: z.object({
125
129
  recommendations: z.array(z.string()),
126
130
  packingList: z.array(z.string()),
127
- weatherNotes: z.string()
128
- })
131
+ weatherNotes: z.string(),
132
+ }),
129
133
  });
130
134
 
131
- // The AI will automatically call weatherTool if needed
135
+ // The AI will automatically call getWeather if needed
132
136
  const advice = await travelAdvisor.get(
133
137
  'result',
134
138
  { destination: 'Paris' },
135
139
  { execute: yourAIExecutor, messages: [] }
136
140
  );
141
+
142
+ console.log(advice?.recommendations);
137
143
  ```
138
144
 
139
145
  ### Streaming Responses
140
146
 
141
147
  ```typescript
142
- // Stream content only
148
+ // Stream text content only
143
149
  for await (const chunk of summarizer.get(
144
150
  'streamContent',
145
151
  { text: 'Long text...' },
@@ -155,11 +161,13 @@ for await (const event of summarizer.get(
155
161
  { stream: yourAIStreamer, messages: [] }
156
162
  )) {
157
163
  if (event.type === 'textPartial') {
158
- console.log('Text:', event.content);
164
+ process.stdout.write(event.content);
159
165
  } else if (event.type === 'toolStart') {
160
166
  console.log('Tool started:', event.tool.name);
161
167
  } else if (event.type === 'toolOutput') {
162
168
  console.log('Tool result:', event.result);
169
+ } else if (event.type === 'complete') {
170
+ console.log('Final output:', event.output);
163
171
  }
164
172
  }
165
173
  ```
@@ -168,40 +176,39 @@ for await (const event of summarizer.get(
168
176
 
169
177
  ```typescript
170
178
  import { Agent } from '@aeye/core';
179
+ import z from 'zod';
171
180
 
181
+ // Assume searchTool, summarizePrompt, and analyzePrompt are defined elsewhere
172
182
  const researchAgent = new Agent({
173
183
  name: 'researcher',
174
- description: 'Conducts research on topics',
175
-
176
- refs: [searchTool, summarizeTool, analyzeTool],
177
-
178
- call: async (input: { topic: string }, [search, summarize, analyze], ctx) => {
184
+ description: 'Conducts research on a topic',
185
+
186
+ refs: [searchTool, summarizePrompt, analyzePrompt] as const,
187
+
188
+ call: async (
189
+ input: { topic: string },
190
+ [search, summarize, analyze],
191
+ ctx
192
+ ) => {
179
193
  // Step 1: Search for information
180
- const searchResults = await search.run(
181
- { query: input.topic, limit: 5 },
182
- ctx
183
- );
184
-
185
- // Step 2: Summarize findings
186
- const summaries = [];
187
- for (const result of searchResults) {
188
- const summary = await summarize.get(
189
- 'result',
190
- { text: result.content },
191
- ctx
192
- );
193
- summaries.push(summary);
194
+ const searchResults = await search.run({ query: input.topic, limit: 5 }, ctx);
195
+
196
+ // Step 2: Summarize each result
197
+ const summaries: string[] = [];
198
+ for (const result of searchResults.items) {
199
+ const summary = await summarize.get('result', { text: result.content }, ctx);
200
+ summaries.push(summary?.summary ?? '');
194
201
  }
195
-
196
- // Step 3: Analyze and synthesize
202
+
203
+ // Step 3: Synthesize into a final analysis
197
204
  const analysis = await analyze.get(
198
205
  'result',
199
206
  { topic: input.topic, sources: summaries },
200
207
  ctx
201
208
  );
202
-
209
+
203
210
  return analysis;
204
- }
211
+ },
205
212
  });
206
213
 
207
214
  const research = await researchAgent.run(
@@ -212,15 +219,15 @@ const research = await researchAgent.run(
212
219
 
213
220
  ## Prompt Modes
214
221
 
215
- The `get` method supports different execution modes:
222
+ The `get(mode, input?, ctx?)` method supports different execution modes:
216
223
 
217
224
  | Mode | Description | Returns |
218
225
  |------|-------------|---------|
219
- | `'result'` | Get final structured output | `TOutput` |
220
- | `'tools'` | Get tool call results | `PromptToolOutput[]` |
221
- | `'stream'` | Stream all events | `AsyncGenerator<PromptEvent>` |
222
- | `'streamTools'` | Stream tool outputs | `AsyncGenerator<PromptToolOutput>` |
223
- | `'streamContent'` | Stream text content only | `AsyncGenerator<string>` |
226
+ | `'result'` | Await the final structured output | `Promise<TOutput \| undefined>` |
227
+ | `'tools'` | Await all tool call results | `Promise<PromptToolOutput[] \| undefined>` |
228
+ | `'stream'` | Stream all events | `AsyncGenerator<PromptEvent<TOutput, TTools>, TOutput \| undefined>` |
229
+ | `'streamTools'` | Stream tool output events | `AsyncGenerator<PromptToolOutput<TTools>, TOutput \| undefined>` |
230
+ | `'streamContent'` | Stream text content only | `AsyncGenerator<string, TOutput \| undefined>` |
224
231
 
225
232
  ```typescript
226
233
  // Get structured result
@@ -231,95 +238,106 @@ const tools = await prompt.get('tools', input, ctx);
231
238
 
232
239
  // Stream everything
233
240
  for await (const event of prompt.get('stream', input, ctx)) {
234
- // Handle different event types
241
+ // event.type is one of: 'request', 'textPartial', 'text', 'toolStart',
242
+ // 'toolOutput', 'toolError', 'message', 'complete', 'usage', ...
243
+ }
244
+
245
+ // Stream text only
246
+ for await (const text of prompt.get('streamContent', input, ctx)) {
247
+ process.stdout.write(text);
235
248
  }
236
249
  ```
237
250
 
238
251
  ## Tool Execution Modes
239
252
 
240
- Control how tools are executed:
253
+ Control how tools are executed by the prompt:
241
254
 
242
255
  ```typescript
243
256
  const prompt = new Prompt({
244
257
  name: 'multi-tool',
245
258
  description: 'Uses multiple tools',
246
- content: 'Analyze the data',
259
+ content: 'Analyze this data using the available tools.',
247
260
  tools: [tool1, tool2, tool3],
248
-
249
- // Tool execution mode
250
- toolExecution: 'parallel', // 'sequential' | 'parallel' | 'immediate'
251
-
252
- // Retry configuration
253
- toolRetries: 2, // Retry failed tools
254
- toolIterations: 3, // Max iterations for tool calls
255
- toolsMax: 5, // Max total tool calls
261
+
262
+ // 'sequential' | 'parallel' | 'immediate' (default: 'immediate')
263
+ toolExecution: 'parallel',
264
+
265
+ // Number of times to retry a failed tool call (default: 2)
266
+ toolRetries: 2,
267
+ // Max tool-calling iterations per request (default: 3)
268
+ toolIterations: 3,
269
+ // Max total successful tool calls before stopping (no strict limit by default)
270
+ toolsMax: 5,
256
271
  });
257
272
  ```
258
273
 
259
- - **`sequential`** - Wait for each tool to finish before continuing
260
- - **`parallel`** - Start all tools at once, wait for all to complete
261
- - **`immediate`** - Start tools immediately as they're available
274
+ - **`sequential`** - Wait for each tool to complete before starting the next
275
+ - **`parallel`** - Start all pending tools at once and wait for all to complete
276
+ - **`immediate`** - Start each tool as soon as its call is received (default)
262
277
 
263
278
  ## Advanced Features
264
279
 
265
280
  ### Context-Aware Tools
266
281
 
282
+ Tool schemas and applicability can depend on the current context:
283
+
267
284
  ```typescript
285
+ interface MyContext {
286
+ userRole: 'admin' | 'viewer';
287
+ isAuthenticated: boolean;
288
+ }
289
+
268
290
  const contextTool = new Tool({
269
- name: 'contextAware',
270
- description: 'A context-aware tool',
271
- instructions: 'Use this tool...',
272
-
273
- // Schema can depend on context
274
- schema: (ctx) => {
291
+ name: 'manageData',
292
+ description: 'Manage data records',
293
+ instructions: 'Use this tool to read or write data.',
294
+
295
+ // Schema varies based on the caller's role
296
+ schema: (ctx: Context<MyContext, {}>) => {
275
297
  if (ctx.userRole === 'admin') {
276
- return z.object({
277
- action: z.enum(['read', 'write', 'delete'])
278
- });
298
+ return z.object({ action: z.enum(['read', 'write', 'delete']) });
279
299
  }
280
- return z.object({
281
- action: z.enum(['read'])
282
- });
300
+ return z.object({ action: z.enum(['read']) });
283
301
  },
284
-
285
- // Check if tool is applicable
286
- applicable: (ctx) => {
287
- return ctx.isAuthenticated === true;
302
+
303
+ // Tool is only available to authenticated users
304
+ applicable: (ctx: Context<MyContext, {}>) => ctx.isAuthenticated,
305
+
306
+ call: async (input, _refs, ctx) => {
307
+ return { action: input.action, executedBy: ctx.userRole };
288
308
  },
289
-
290
- call: async (input, refs, ctx) => {
291
- // Implementation with context access
292
- }
293
309
  });
294
310
  ```
295
311
 
296
312
  ### Custom Validation
297
313
 
314
+ Add business-logic validation beyond Zod schema checks:
315
+
298
316
  ```typescript
299
- const validatedTool = new Tool({
317
+ const placeOrder = new Tool({
300
318
  name: 'placeOrder',
301
- description: 'Place an order',
302
-
319
+ description: 'Place a product order',
320
+
303
321
  schema: z.object({
304
322
  itemId: z.string(),
305
- quantity: z.number().min(1)
323
+ quantity: z.number().int().min(1),
306
324
  }),
307
-
308
- // Additional validation beyond schema
325
+
326
+ // Runs after schema parsing succeeds; throw to trigger re-prompting
309
327
  validate: async (input, ctx) => {
310
328
  const inventory = await checkInventory(input.itemId);
311
329
  if (inventory < input.quantity) {
312
- throw new Error(`Only ${inventory} items available`);
330
+ throw new Error(`Only ${inventory} units of ${input.itemId} are available.`);
313
331
  }
314
332
  },
315
-
316
- call: async (input, refs, ctx) => {
317
- // Place order
318
- }
333
+
334
+ call: async (input, _refs, _ctx) => {
335
+ return { orderId: crypto.randomUUID(), ...input };
336
+ },
319
337
  });
320
338
  ```
321
339
 
322
- ### Event Tracking
340
+ ### Event Tracking with `withEvents`
323
341
 
324
342
  ```typescript
325
343
  import { withEvents } from '@aeye/core';
@@ -327,164 +345,190 @@ import { withEvents } from '@aeye/core';
327
345
  const runner = withEvents({
328
346
  onStatus: (instance) => {
329
347
  console.log(`${instance.component.name}: ${instance.status}`);
330
- if (instance.status === 'completed') {
331
- const duration = instance.completed - instance.started;
332
- console.log(`Took ${duration}ms`);
348
+ if (instance.status === 'completed' && instance.completed && instance.running) {
349
+ console.log(` Took ${instance.completed - instance.running}ms`);
333
350
  }
334
351
  },
335
-
336
- onPromptEvent: (instance, event) => {
352
+
353
+ onPromptEvent: (_instance, event) => {
337
354
  if (event.type === 'usage') {
338
355
  console.log('Tokens used:', event.usage);
339
356
  }
340
- }
357
+ },
341
358
  });
342
359
 
343
360
  const result = await prompt.get(
344
361
  'result',
345
362
  { text: 'Hello' },
346
- {
347
- execute: yourAIExecutor,
348
- messages: [],
349
- runner
350
- }
363
+ { execute: yourAIExecutor, messages: [], runner }
351
364
  );
352
365
  ```
353
366
 
354
367
  ### Token Management
355
368
 
356
- Automatic conversation trimming when token limits are reached:
369
+ The prompt automatically trims old messages when the context window is full:
357
370
 
358
371
  ```typescript
359
- const context = {
360
- execute: yourAIExecutor,
361
- messages: conversationHistory,
362
-
363
- // Token configuration
364
- defaultCompletionTokens: 2048,
365
-
366
- // Custom token estimation
367
- estimateTokens: (message) => {
368
- return message.content.length / 4; // Rough estimate
369
- }
370
- };
372
+ const result = await prompt.get(
373
+ 'result',
374
+ { text: 'Query' },
375
+ {
376
+ execute: yourAIExecutor,
377
+ messages: conversationHistory, // Will be trimmed if needed
371
378
 
372
- // Prompt will automatically trim messages if needed
373
- const result = await prompt.get('result', { text: 'Query' }, context);
379
+ // Reserve this many tokens for the model's response
380
+ maxOutputTokens: 2048,
381
+
382
+ // Custom per-message token estimator
383
+ estimateUsage: (message) => ({
384
+ text: { input: Math.ceil((message.content as string).length / 4) }
385
+ }),
386
+ }
387
+ );
374
388
  ```
375
389
 
376
390
  ### Dynamic Reconfiguration
377
391
 
392
+ Use `reconfig` to adapt the prompt based on runtime statistics:
393
+
378
394
  ```typescript
379
395
  const adaptivePrompt = new Prompt({
380
396
  name: 'adaptive',
381
- description: 'Adapts based on execution stats',
397
+ description: 'Adapts based on runtime stats',
382
398
  content: 'Solve: {{problem}}',
383
399
  schema: z.object({ solution: z.string() }),
384
-
385
- // Adjust configuration during execution
386
- reconfig: (stats, ctx) => {
387
- // If tools are failing, change strategy
400
+
401
+ reconfig: (stats, _ctx) => {
402
+ // If many tool errors occurred, switch to one-at-a-time mode
388
403
  if (stats.toolCallErrors > 3) {
389
- return {
390
- config: { toolsOneAtATime: true },
391
- maxIterations: 2
392
- };
404
+ return { config: { toolsOneAtATime: true } };
393
405
  }
394
406
  return {};
395
- }
407
+ },
396
408
  });
397
409
  ```
398
410
 
411
+ ## Model Capabilities
412
+
413
+ @aeye uses a capability system for automatic model selection. Models advertise their capabilities and the library selects the best match for each request:
414
+
415
+ | Capability | Description |
416
+ |------------|-------------|
417
+ | `chat` | Basic text completion |
418
+ | `streaming` | Real-time response streaming |
419
+ | `image` | Image generation |
420
+ | `vision` | Image understanding |
421
+ | `audio` | Text-to-speech |
422
+ | `hearing` | Speech-to-text |
423
+ | `embedding` | Text embeddings |
424
+ | `tools` | Function/tool calling |
425
+ | `json` | JSON output mode |
426
+ | `structured` | Structured outputs with schemas |
427
+ | `reasoning` | Extended chain-of-thought reasoning |
428
+ | `zdr` | Zero data retention |
429
+
399
430
  ## API Reference
400
431
 
401
432
  ### Prompt
402
433
 
403
434
  ```typescript
404
- class Prompt<TContext, TMetadata, TName, TInput, TOutput, TTools>
435
+ class Prompt<TContext, TMetadata, TName extends string, TInput extends object, TOutput extends object | string, TTools extends Tuple<ToolCompatible<TContext, TMetadata>>>
405
436
  ```
406
437
 
407
- **Constructor Options:**
438
+ **Constructor Options (`PromptInput`):**
408
439
  - `name: string` - Unique identifier
409
440
  - `description: string` - Purpose description
410
- - `content: string` - Handlebars template
411
- - `input?: Function` - Transform input for template
412
- - `schema?: ZodType | Function` - Output schema
413
- - `config?: Partial<Request> | Function` - AI request config
414
- - `tools?: Tool[]` - Available tools
415
- - `toolExecution?: 'sequential' | 'parallel' | 'immediate'`
416
- - `toolRetries?: number` - Retry failed tools
417
- - `toolIterations?: number` - Max tool call iterations
418
- - `outputRetries?: number` - Retry invalid outputs
419
- - `metadata?: TMetadata` - Execution metadata
420
- - `validate?: Function` - Post-validation hook
421
- - `applicable?: Function` - Applicability check
441
+ - `content: string` - Handlebars template for the system/user message
442
+ - `input?: (input: TInput, ctx: Context<TContext, TMetadata>) => Record<string, any>` - Maps raw input to template variables
443
+ - `schema?: ZodType<TOutput> | ((input: TInput | undefined, ctx: Context<TContext, TMetadata>) => ZodType<TOutput> | false)` - Output schema; `false` disables structured output
444
+ - `strict?: boolean` - Enforce strict Zod schema (default: `true`)
445
+ - `config?: Partial<Request> | ((input: TInput | undefined, ctx: Context<TContext, TMetadata>) => Partial<Request> | false)` - AI request overrides
446
+ - `tools?: TTools` - Tools the prompt may call
447
+ - `toolExecution?: 'sequential' | 'parallel' | 'immediate'` - Tool execution mode
448
+ - `toolRetries?: number` - Retry count for failed tools (default: 2)
449
+ - `toolIterations?: number` - Max tool-calling iterations (default: 3)
450
+ - `toolsMax?: number` - Max successful tool calls
451
+ - `outputRetries?: number` - Retry count for invalid structured outputs
452
+ - `metadata?: TMetadata` - Static metadata for this prompt
453
+ - `validate?: (output: TOutput, ctx: Context<TContext, TMetadata>) => void | Promise<void>` - Post-parse validation
454
+ - `applicable?: (ctx: Context<TContext, TMetadata>) => boolean | Promise<boolean>` - Availability check
422
455
 
423
456
  **Methods:**
424
- - `get(input, mode, ctx)` - Execute and retrieve output
425
- - `run(input, ctx)` - Execute with full streaming
426
- - `applicable(ctx)` - Check if prompt can run
457
+ - `get(mode: PromptGetType, input?: TInput, ctx?: Context<TContext, TMetadata>): PromptGet<...>` - Execute and return output in the specified mode
458
+ - `run(input?: TInput, ctx?: Context<TContext, TMetadata>): AsyncGenerator<PromptEvent<TOutput, TTools>, TOutput | undefined>` - Full streaming generator
459
+ - `applicable(ctx?: Context<TContext, TMetadata>): Promise<boolean>` - Check if prompt is usable
427
460
 
428
461
  ### Tool
429
462
 
430
463
  ```typescript
431
- class Tool<TContext, TMetadata, TName, TParams, TOutput, TRefs>
464
+ class Tool<TContext, TMetadata, TName extends string, TParams extends object, TOutput, TRefs extends Tuple<ComponentCompatible<TContext, TMetadata>>>
432
465
  ```
433
466
 
434
- **Constructor Options:**
435
- - `name: string` - Unique identifier
436
- - `description: string` - Purpose description
437
- - `instructions?: string` - Handlebars usage instructions
438
- - `schema: ZodType | Function` - Input schema
439
- - `refs?: Component[]` - Referenced components
440
- - `call: Function` - Implementation
441
- - `validate?: Function` - Post-validation hook
442
- - `applicable?: Function` - Applicability check
467
+ **Constructor Options (`ToolInput`):**
468
+ - `name: string` - Unique identifier (shown to the AI model)
469
+ - `description: string` - What the tool does (shown to the AI model)
470
+ - `instructions?: string` - Handlebars template for usage instructions
471
+ - `schema: ZodType<TParams> | ((ctx: Context<TContext, TMetadata>) => ZodType<TParams> | undefined)` - Input parameter schema
472
+ - `strict?: boolean` - Enforce strict Zod schema (default: `true`)
473
+ - `refs?: TRefs` - Referenced components
474
+ - `call: (input: TParams, refs: TRefs, ctx: Context<TContext, TMetadata>) => TOutput` - Implementation
475
+ - `validate?: (input: TParams, ctx: Context<TContext, TMetadata>) => void | Promise<void>` - Post-parse validation
476
+ - `applicable?: (ctx: Context<TContext, TMetadata>) => boolean | Promise<boolean>` - Availability check
443
477
 
444
478
  **Methods:**
445
- - `run(input, ctx)` - Execute the tool
446
- - `compile(ctx)` - Generate tool definition for AI
447
- - `parse(ctx, args)` - Parse and validate arguments
448
- - `applicable(ctx)` - Check if tool can run
479
+ - `run(input?: TParams, ctx?: Context<TContext, TMetadata>): TOutput` - Execute the tool directly
480
+ - `compile(ctx: Context<TContext, TMetadata>): Promise<readonly [string, ToolDefinition] | undefined>` - Generate AI tool definition
481
+ - `applicable(ctx?: Context<TContext, TMetadata>): Promise<boolean>` - Check if tool is usable
449
482
 
450
483
  ### Agent
451
484
 
452
485
  ```typescript
453
- class Agent<TContext, TMetadata, TName, TInput, TOutput, TRefs>
486
+ class Agent<TContext, TMetadata, TName extends string, TInput extends object, TOutput, TRefs extends Tuple<ComponentCompatible<TContext, TMetadata>>>
454
487
  ```
455
488
 
456
- **Constructor Options:**
489
+ **Constructor Options (`AgentInput`):**
457
490
  - `name: string` - Unique identifier
458
491
  - `description: string` - Purpose description
459
- - `refs?: Component[]` - Referenced components
460
- - `call: Function` - Implementation with refs
461
- - `applicable?: Function` - Applicability check
492
+ - `refs: TRefs` - Components this agent uses
493
+ - `call: (input: TInput, refs: TRefs, ctx: Context<TContext, TMetadata>) => TOutput` - Implementation
494
+ - `applicable?: (ctx: Context<TContext, TMetadata>) => boolean | Promise<boolean>` - Availability check
462
495
 
463
496
  **Methods:**
464
- - `run(input, ctx)` - Execute the agent
465
- - `applicable(ctx)` - Check if agent can run
497
+ - `run(input?: TInput, ctx?: Context<TContext, TMetadata>): TOutput` - Execute the agent
498
+ - `applicable(ctx?: Context<TContext, TMetadata>): Promise<boolean>` - Check if agent is usable
466
499
 
467
500
  ## Context Structure
468
501
 
502
+ The `Context<TContext, TMetadata>` type combines your custom context with the core execution context:
503
+
469
504
  ```typescript
470
- interface Context<TContext, TMetadata> {
471
- // Required: AI execution
472
- execute?: Executor<TContext, TMetadata>;
473
- stream?: Streamer<TContext, TMetadata>;
474
-
475
- // Messages
476
- messages: Message[];
477
-
478
- // Token management
479
- defaultCompletionTokens?: number;
480
- estimateTokens?: (message: Message) => number;
481
-
482
- // Execution control
505
+ // Core fields available in every context
506
+ interface CoreContextFields {
507
+ // AI executor for non-streaming requests
508
+ execute?: (request: Request, ctx: TContext, metadata?: TMetadata, signal?: AbortSignal) => Promise<Response>;
509
+ // AI streamer for streaming requests
510
+ stream?: (request: Request, ctx: TContext, metadata?: TMetadata, signal?: AbortSignal) => AsyncGenerator<Chunk, Response>;
511
+
512
+ // Conversation history passed to the model
513
+ messages?: Message[];
514
+
515
+ // Reserve N tokens for the model's output (used for context trimming)
516
+ maxOutputTokens?: number;
517
+ // Override context window size (used for context trimming)
518
+ contextWindow?: number;
519
+
520
+ // Custom per-message usage estimator
521
+ estimateUsage?: (message: Message) => Usage | undefined;
522
+
523
+ // Number of output-format retries (default: 2)
524
+ outputRetries?: number;
525
+ // Number of context-trimming retries (default: 1)
526
+ forgetRetries?: number;
527
+
528
+ // Abort signal for cancellation
483
529
  signal?: AbortSignal;
530
+ // Event runner for tracking component lifecycle
484
531
  runner?: Events;
485
-
486
- // User context (TContext) spreads here
487
- [key: string]: any;
488
532
  }
489
533
  ```
490
534
 
@@ -493,13 +537,17 @@ interface Context<TContext, TMetadata> {
493
537
  ```typescript
494
538
  interface Request {
495
539
  messages: Message[];
496
- temperature?: number; // 0-2
497
- topP?: number; // 0-1
540
+ temperature?: number; // 0.0–2.0
541
+ topP?: number; // 0.0–1.0
498
542
  maxTokens?: number;
499
543
  stop?: string | string[];
500
544
  tools?: ToolDefinition[];
501
- toolChoice?: 'auto' | 'required' | 'none' | { tool: string };
502
- responseFormat?: 'text' | 'json' | ResponseFormat;
545
+ toolChoice?: 'auto' | 'required' | 'none' | { type: 'tool'; name: string };
546
+ responseFormat?: 'text' | 'json' | { type: 'json_schema'; schema: object; name: string; strict?: boolean };
547
+ reason?: { effort?: 'low' | 'medium' | 'high'; maxTokens?: number };
548
+ frequencyPenalty?: number;
549
+ presencePenalty?: number;
550
+ seed?: number;
503
551
  // ... more options
504
552
  }
505
553
  ```
@@ -507,15 +555,13 @@ interface Request {
507
555
  ## Best Practices
508
556
 
509
557
  1. **Type Safety** - Use TypeScript generics for context and metadata
510
- 2. **Schema Validation** - Use Zod for robust input/output validation
511
- 3. **Error Handling** - Wrap component execution in try-catch blocks
512
- 4. **Token Management** - Provide `estimateTokens` for accurate trimming
513
- 5. **Tool Organization** - Group related tools in agents
514
- 6. **Testing** - Unit test tools and prompts independently
515
- 7. **Documentation** - Document template variables and schemas
516
- 8. **Context Minimization** - Pass only necessary data in context
517
- 9. **Streaming** - Use streaming for better UX with long responses
518
- 10. **Validation** - Use `validate` hooks for business logic validation
558
+ 2. **Schema Validation** - Use Zod for robust input/output validation
559
+ 3. **Token Management** - Supply `estimateUsage` for accurate context trimming
560
+ 4. **Tool Organization** - Group related tools and use agents to orchestrate them
561
+ 5. **Testing** - Unit test tools and prompts independently
562
+ 6. **Context Minimization** - Pass only necessary data in context
563
+ 7. **Streaming** - Use streaming for better UX with long responses
564
+ 8. **Validation** - Use `validate` hooks for business-logic checks after schema parsing
519
565
 
520
566
  ## Examples
521
567
 
@@ -532,24 +578,31 @@ See the `src/__tests__` directory for comprehensive examples:
532
578
  Full type inference across component hierarchies:
533
579
 
534
580
  ```typescript
535
- // Types are automatically inferred
581
+ import { Prompt, Tool } from '@aeye/core';
582
+ import z from 'zod';
583
+
584
+ // Output type is inferred from the schema
536
585
  const prompt = new Prompt({
537
586
  name: 'example',
538
- schema: z.object({ result: z.string() })
539
- // ...
587
+ description: 'Example prompt',
588
+ content: 'Say hello to {{name}}.',
589
+ input: (input: { name: string }) => ({ name: input.name }),
590
+ schema: z.object({ greeting: z.string() }),
540
591
  });
541
592
 
542
- // TypeScript knows the output type
543
- const output = await prompt.get({}); // { result: string }
593
+ // result is typed as { greeting: string } | undefined
594
+ const result = await prompt.get('result', { name: 'World' }, ctx);
595
+ console.log(result?.greeting);
544
596
 
545
- // Tool parameters are type-safe
546
- const tool = new Tool({
547
- name: 'math',
597
+ // Tool call parameters are type-safe
598
+ const mathTool = new Tool({
599
+ name: 'add',
600
+ description: 'Adds two numbers',
548
601
  schema: z.object({ a: z.number(), b: z.number() }),
549
- call: (input, refs, ctx) => {
550
- // input is typed as { a: number, b: number }
602
+ call: (input, _refs, _ctx) => {
603
+ // input is typed as { a: number; b: number }
551
604
  return input.a + input.b;
552
- }
605
+ },
553
606
  });
554
607
  ```
555
608
 
@@ -563,6 +616,7 @@ GPL-3.0 © [ClickerMonkey](https://github.com/ClickerMonkey)
563
616
 
564
617
  ## Links
565
618
 
619
+ - [Root README](../../README.md)
620
+ - [@aeye/ai README](../ai/README.md)
566
621
  - [GitHub Repository](https://github.com/ClickerMonkey/aeye)
567
- - [Documentation](https://github.com/ClickerMonkey/aeye/tree/main/packages/core)
568
622
  - [Issue Tracker](https://github.com/ClickerMonkey/aeye/issues)