@agentuity/runtime 0.0.68 → 0.0.70
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/AGENTS.md +130 -10
- package/README.md +6 -8
- package/dist/_config.d.ts +16 -0
- package/dist/_config.d.ts.map +1 -1
- package/dist/_config.js +16 -0
- package/dist/_config.js.map +1 -1
- package/dist/_context.d.ts +17 -15
- package/dist/_context.d.ts.map +1 -1
- package/dist/_context.js +17 -8
- package/dist/_context.js.map +1 -1
- package/dist/_server.d.ts.map +1 -1
- package/dist/_server.js +27 -14
- package/dist/_server.js.map +1 -1
- package/dist/_services.d.ts.map +1 -1
- package/dist/_services.js +2 -29
- package/dist/_services.js.map +1 -1
- package/dist/_validation.d.ts +89 -0
- package/dist/_validation.d.ts.map +1 -0
- package/dist/_validation.js +29 -0
- package/dist/_validation.js.map +1 -0
- package/dist/agent.d.ts +476 -104
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +293 -97
- package/dist/agent.js.map +1 -1
- package/dist/app.d.ts +6 -18
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -1
- package/dist/app.js.map +1 -1
- package/dist/eval.d.ts +4 -4
- package/dist/eval.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/otel/config.d.ts.map +1 -1
- package/dist/otel/config.js +5 -2
- package/dist/otel/config.js.map +1 -1
- package/dist/otel/console.d.ts +10 -6
- package/dist/otel/console.d.ts.map +1 -1
- package/dist/otel/console.js +31 -13
- package/dist/otel/console.js.map +1 -1
- package/dist/otel/logger.d.ts.map +1 -1
- package/dist/otel/logger.js +0 -19
- package/dist/otel/logger.js.map +1 -1
- package/dist/otel/otel.d.ts +2 -1
- package/dist/otel/otel.d.ts.map +1 -1
- package/dist/otel/otel.js +28 -15
- package/dist/otel/otel.js.map +1 -1
- package/dist/services/local/_db.d.ts.map +1 -1
- package/dist/services/local/_db.js +1 -22
- package/dist/services/local/_db.js.map +1 -1
- package/dist/services/local/_router.d.ts.map +1 -1
- package/dist/services/local/_router.js +0 -32
- package/dist/services/local/_router.js.map +1 -1
- package/dist/services/local/index.d.ts +0 -1
- package/dist/services/local/index.d.ts.map +1 -1
- package/dist/services/local/index.js +0 -1
- package/dist/services/local/index.js.map +1 -1
- package/dist/session.d.ts +18 -2
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +6 -0
- package/dist/session.js.map +1 -1
- package/dist/validator.d.ts +140 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +146 -0
- package/dist/validator.js.map +1 -0
- package/dist/workbench.d.ts.map +1 -1
- package/dist/workbench.js +38 -29
- package/dist/workbench.js.map +1 -1
- package/package.json +6 -6
- package/src/_config.ts +19 -0
- package/src/_context.ts +25 -31
- package/src/_server.ts +30 -14
- package/src/_services.ts +0 -28
- package/src/_validation.ts +119 -0
- package/src/agent.ts +872 -272
- package/src/app.ts +5 -18
- package/src/eval.ts +6 -6
- package/src/index.ts +3 -1
- package/src/otel/config.ts +5 -2
- package/src/otel/console.ts +34 -20
- package/src/otel/logger.ts +0 -18
- package/src/otel/otel.ts +43 -29
- package/src/services/local/_db.ts +1 -27
- package/src/services/local/_router.ts +0 -46
- package/src/services/local/index.ts +0 -1
- package/src/session.ts +22 -2
- package/src/validator.ts +277 -0
- package/src/workbench.ts +38 -32
- package/dist/services/local/objectstore.d.ts +0 -19
- package/dist/services/local/objectstore.d.ts.map +0 -1
- package/dist/services/local/objectstore.js +0 -117
- package/dist/services/local/objectstore.js.map +0 -1
- package/src/services/local/objectstore.ts +0 -177
package/src/agent.ts
CHANGED
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
import {
|
|
3
3
|
StructuredError,
|
|
4
4
|
type KeyValueStorage,
|
|
5
|
-
type ObjectStorage,
|
|
6
5
|
type StandardSchemaV1,
|
|
7
6
|
type StreamStorage,
|
|
8
7
|
type VectorStorage,
|
|
8
|
+
type InferOutput,
|
|
9
9
|
toCamelCase,
|
|
10
10
|
} from '@agentuity/core';
|
|
11
11
|
import { context, SpanStatusCode, type Tracer, trace } from '@opentelemetry/api';
|
|
12
12
|
import type { Context, MiddlewareHandler } from 'hono';
|
|
13
|
-
import {
|
|
13
|
+
import type { Handler } from 'hono/types';
|
|
14
|
+
import { validator } from 'hono/validator';
|
|
15
|
+
import { AGENT_RUNTIME, INTERNAL_AGENT, CURRENT_AGENT } from './_config';
|
|
16
|
+
import {
|
|
17
|
+
getAgentContext,
|
|
18
|
+
setupRequestAgentContext,
|
|
19
|
+
type RequestAgentContextArgs,
|
|
20
|
+
} from './_context';
|
|
14
21
|
import type { Logger } from './logger';
|
|
15
|
-
import type {
|
|
16
|
-
Eval,
|
|
17
|
-
EvalContext,
|
|
18
|
-
EvalRunResult,
|
|
19
|
-
EvalMetadata,
|
|
20
|
-
EvalFunction,
|
|
21
|
-
ExternalEvalMetadata,
|
|
22
|
-
} from './eval';
|
|
22
|
+
import type { Eval, EvalContext, EvalRunResult, EvalFunction } from './eval';
|
|
23
23
|
import { internal } from './logger/internal';
|
|
24
24
|
import { getApp } from './app';
|
|
25
25
|
import type { Thread, Session } from './session';
|
|
@@ -29,6 +29,7 @@ import { getEvalRunEventProvider } from './_services';
|
|
|
29
29
|
import * as runtimeConfig from './_config';
|
|
30
30
|
import type { EvalRunStartEvent } from '@agentuity/core';
|
|
31
31
|
import type { AppState } from './index';
|
|
32
|
+
import { validateSchema, formatValidationIssues } from './_validation';
|
|
32
33
|
|
|
33
34
|
export type AgentEventName = 'started' | 'completed' | 'errored';
|
|
34
35
|
|
|
@@ -50,24 +51,36 @@ export type AgentEventCallback<TAgent extends Agent<any, any, any>> =
|
|
|
50
51
|
data: Error
|
|
51
52
|
) => Promise<void> | void);
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Runtime state container for agents and event listeners.
|
|
56
|
+
* Isolates global state into context for better testing.
|
|
57
|
+
*/
|
|
58
|
+
export interface AgentRuntimeState {
|
|
59
|
+
agents: Map<string, Agent<any, any, any, any, any>>;
|
|
60
|
+
agentConfigs: Map<string, unknown>;
|
|
61
|
+
agentEventListeners: WeakMap<
|
|
62
|
+
Agent<any, any, any, any, any>,
|
|
63
|
+
Map<AgentEventName, Set<AgentEventCallback<any>>>
|
|
64
|
+
>;
|
|
65
|
+
}
|
|
66
|
+
|
|
53
67
|
/**
|
|
54
68
|
* Context object passed to every agent handler providing access to runtime services and state.
|
|
55
69
|
*
|
|
56
70
|
* @template TAgentRegistry - Registry of all available agents (auto-generated, strongly-typed)
|
|
57
|
-
* @template TCurrent - Current agent runner type
|
|
58
|
-
* @template TParent - Parent agent runner type (if called from another agent)
|
|
59
71
|
* @template TConfig - Agent-specific configuration type from setup function
|
|
60
72
|
* @template TAppState - Application-wide state type from createApp
|
|
61
73
|
*
|
|
62
74
|
* @example
|
|
63
75
|
* ```typescript
|
|
64
|
-
* const agent = createAgent({
|
|
76
|
+
* const agent = createAgent('my-agent', {
|
|
65
77
|
* handler: async (ctx, input) => {
|
|
66
78
|
* // Logging
|
|
67
79
|
* ctx.logger.info('Processing request', { input });
|
|
68
80
|
*
|
|
69
|
-
* // Call another agent
|
|
70
|
-
*
|
|
81
|
+
* // Call another agent (import it directly)
|
|
82
|
+
* import otherAgent from './other-agent';
|
|
83
|
+
* const result = await otherAgent.run({ data: input });
|
|
71
84
|
*
|
|
72
85
|
* // Store data
|
|
73
86
|
* await ctx.kv.set('key', { value: result });
|
|
@@ -86,12 +99,17 @@ export type AgentEventCallback<TAgent extends Agent<any, any, any>> =
|
|
|
86
99
|
* ```
|
|
87
100
|
*/
|
|
88
101
|
export interface AgentContext<
|
|
89
|
-
|
|
90
|
-
TCurrent extends AgentRunner<any, any, any> | undefined = AgentRunner<any, any, any> | undefined,
|
|
91
|
-
TParent extends AgentRunner<any, any, any> | undefined = AgentRunner<any, any, any> | undefined,
|
|
102
|
+
_TAgentRegistry extends AgentRegistry = AgentRegistry,
|
|
92
103
|
TConfig = unknown,
|
|
93
104
|
TAppState = Record<string, never>,
|
|
94
105
|
> {
|
|
106
|
+
/**
|
|
107
|
+
* Internal runtime state (agents, configs, event listeners).
|
|
108
|
+
* Stored with Symbol key to prevent accidental access.
|
|
109
|
+
* Use getAgentRuntime(ctx) to access.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
[AGENT_RUNTIME]: AgentRuntimeState;
|
|
95
113
|
/**
|
|
96
114
|
* Schedule a background task that continues after the response is sent.
|
|
97
115
|
* Useful for cleanup, logging, or async operations that don't block the response.
|
|
@@ -108,34 +126,6 @@ export interface AgentContext<
|
|
|
108
126
|
*/
|
|
109
127
|
waitUntil: (promise: Promise<void> | (() => void | Promise<void>)) => void;
|
|
110
128
|
|
|
111
|
-
/**
|
|
112
|
-
* Registry of all agents in the application. Strongly-typed and auto-generated.
|
|
113
|
-
* Use to call other agents from within your handler.
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```typescript
|
|
117
|
-
* const emailResult = await ctx.agent.email.run({ to: 'user@example.com' });
|
|
118
|
-
* const smsResult = await ctx.agent.sms.run({ phone: '+1234567890' });
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
agent: TAgentRegistry;
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Information about the currently executing agent.
|
|
125
|
-
*/
|
|
126
|
-
current: TCurrent;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Information about the parent agent (if this agent was called by another agent).
|
|
130
|
-
* Use ctx.agent.parentName for strongly-typed access.
|
|
131
|
-
*/
|
|
132
|
-
parent: TParent;
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Name of the current agent being executed.
|
|
136
|
-
*/
|
|
137
|
-
agentName: AgentName;
|
|
138
|
-
|
|
139
129
|
/**
|
|
140
130
|
* Structured logger with OpenTelemetry integration.
|
|
141
131
|
* Logs are automatically correlated with traces.
|
|
@@ -184,19 +174,6 @@ export interface AgentContext<
|
|
|
184
174
|
*/
|
|
185
175
|
kv: KeyValueStorage;
|
|
186
176
|
|
|
187
|
-
/**
|
|
188
|
-
* Object storage for files and blobs (S3-compatible).
|
|
189
|
-
*
|
|
190
|
-
* @example
|
|
191
|
-
* ```typescript
|
|
192
|
-
* await ctx.objectstore.put('images/photo.jpg', buffer);
|
|
193
|
-
* const file = await ctx.objectstore.get('images/photo.jpg');
|
|
194
|
-
* await ctx.objectstore.delete('images/photo.jpg');
|
|
195
|
-
* const objects = await ctx.objectstore.list('images/');
|
|
196
|
-
* ```
|
|
197
|
-
*/
|
|
198
|
-
objectstore: ObjectStorage;
|
|
199
|
-
|
|
200
177
|
/**
|
|
201
178
|
* Stream storage for real-time data streams and logs.
|
|
202
179
|
*
|
|
@@ -283,13 +260,13 @@ export interface AgentContext<
|
|
|
283
260
|
|
|
284
261
|
type InternalAgentMetadata = {
|
|
285
262
|
/**
|
|
286
|
-
* the unique
|
|
263
|
+
* the unique name for the agent (user-provided).
|
|
287
264
|
*/
|
|
288
|
-
|
|
265
|
+
name: string;
|
|
289
266
|
/**
|
|
290
|
-
* the unique identifier for this project
|
|
267
|
+
* the unique identifier for this project, agent and deployment.
|
|
291
268
|
*/
|
|
292
|
-
|
|
269
|
+
id: string;
|
|
293
270
|
/**
|
|
294
271
|
* the unique identifier for this agent across multiple deployments
|
|
295
272
|
*/
|
|
@@ -315,10 +292,6 @@ type InternalAgentMetadata = {
|
|
|
315
292
|
};
|
|
316
293
|
|
|
317
294
|
type ExternalAgentMetadata = {
|
|
318
|
-
/**
|
|
319
|
-
* the human readable name for the agent.
|
|
320
|
-
*/
|
|
321
|
-
name: string;
|
|
322
295
|
/**
|
|
323
296
|
* the human readable description for the agent
|
|
324
297
|
*/
|
|
@@ -330,25 +303,22 @@ type AgentMetadata = InternalAgentMetadata & ExternalAgentMetadata;
|
|
|
330
303
|
/**
|
|
331
304
|
* Configuration object for creating an agent evaluation function.
|
|
332
305
|
*
|
|
333
|
-
* @template TInput - Input schema type from the
|
|
334
|
-
* @template TOutput - Output schema type from the
|
|
306
|
+
* @template TInput - Input schema type from the agent
|
|
307
|
+
* @template TOutput - Output schema type from the agent
|
|
335
308
|
*/
|
|
336
309
|
export interface CreateEvalConfig<
|
|
337
310
|
TInput extends StandardSchemaV1 | undefined = any,
|
|
338
311
|
TOutput extends StandardSchemaV1 | undefined = any,
|
|
339
312
|
> {
|
|
340
313
|
/**
|
|
341
|
-
* Optional
|
|
314
|
+
* Optional description of what this evaluation does.
|
|
342
315
|
*
|
|
343
316
|
* @example
|
|
344
317
|
* ```typescript
|
|
345
|
-
*
|
|
346
|
-
* name: 'Validate positive output',
|
|
347
|
-
* description: 'Ensures output is greater than zero'
|
|
348
|
-
* }
|
|
318
|
+
* description: 'Ensures output is greater than zero'
|
|
349
319
|
* ```
|
|
350
320
|
*/
|
|
351
|
-
|
|
321
|
+
description?: string;
|
|
352
322
|
|
|
353
323
|
/**
|
|
354
324
|
* Evaluation handler function that tests the agent's behavior.
|
|
@@ -383,8 +353,8 @@ export interface CreateEvalConfig<
|
|
|
383
353
|
* ```
|
|
384
354
|
*/
|
|
385
355
|
handler: EvalFunction<
|
|
386
|
-
TInput extends StandardSchemaV1 ?
|
|
387
|
-
TOutput extends StandardSchemaV1 ?
|
|
356
|
+
TInput extends StandardSchemaV1 ? InferOutput<TInput> : undefined,
|
|
357
|
+
TOutput extends StandardSchemaV1 ? InferOutput<TOutput> : undefined
|
|
388
358
|
>;
|
|
389
359
|
}
|
|
390
360
|
|
|
@@ -392,7 +362,148 @@ export interface CreateEvalConfig<
|
|
|
392
362
|
type CreateEvalMethod<
|
|
393
363
|
TInput extends StandardSchemaV1 | undefined = any,
|
|
394
364
|
TOutput extends StandardSchemaV1 | undefined = any,
|
|
395
|
-
> = (config: CreateEvalConfig<TInput, TOutput>) => Eval<TInput, TOutput>;
|
|
365
|
+
> = (name: string, config: CreateEvalConfig<TInput, TOutput>) => Eval<TInput, TOutput>;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Validator function type with method overloads for different validation scenarios.
|
|
369
|
+
* Provides type-safe validation middleware that integrates with Hono's type system.
|
|
370
|
+
*
|
|
371
|
+
* This validator automatically validates incoming JSON request bodies using StandardSchema-compatible
|
|
372
|
+
* schemas (Zod, Valibot, ArkType, etc.) and provides full TypeScript type inference for validated data
|
|
373
|
+
* accessible via `c.req.valid('json')`.
|
|
374
|
+
*
|
|
375
|
+
* The validator returns 400 Bad Request with validation error details if validation fails.
|
|
376
|
+
*
|
|
377
|
+
* @template TInput - Agent's input schema type (StandardSchemaV1 or undefined)
|
|
378
|
+
* @template _TOutput - Agent's output schema type (reserved for future output validation)
|
|
379
|
+
*
|
|
380
|
+
* @example Basic usage with agent's schema
|
|
381
|
+
* ```typescript
|
|
382
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
383
|
+
* const data = c.req.valid('json'); // Fully typed from agent's input schema
|
|
384
|
+
* return c.json(data);
|
|
385
|
+
* });
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* @example Override with custom input schema
|
|
389
|
+
* ```typescript
|
|
390
|
+
* router.post('/custom', agent.validator({ input: z.object({ id: z.string() }) }), async (c) => {
|
|
391
|
+
* const data = c.req.valid('json'); // Typed as { id: string }
|
|
392
|
+
* return c.json(data);
|
|
393
|
+
* });
|
|
394
|
+
* ```
|
|
395
|
+
*/
|
|
396
|
+
export interface AgentValidator<
|
|
397
|
+
TInput extends StandardSchemaV1 | undefined,
|
|
398
|
+
_TOutput extends StandardSchemaV1 | undefined,
|
|
399
|
+
> {
|
|
400
|
+
/**
|
|
401
|
+
* Validates using the agent's input schema (no override).
|
|
402
|
+
* Returns Hono middleware handler that validates JSON request body.
|
|
403
|
+
*
|
|
404
|
+
* @returns Middleware handler with type inference for validated data
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```typescript
|
|
408
|
+
* // Agent has schema: { input: z.object({ name: z.string() }) }
|
|
409
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
410
|
+
* const data = c.req.valid('json'); // { name: string }
|
|
411
|
+
* return c.json({ received: data.name });
|
|
412
|
+
* });
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
(): TInput extends StandardSchemaV1
|
|
416
|
+
? Handler<
|
|
417
|
+
any,
|
|
418
|
+
any,
|
|
419
|
+
{
|
|
420
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
421
|
+
in: {};
|
|
422
|
+
out: { json: InferOutput<TInput> };
|
|
423
|
+
}
|
|
424
|
+
>
|
|
425
|
+
: Handler<any, any, any>;
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Output-only validation override.
|
|
429
|
+
* Validates only the response body (no input validation).
|
|
430
|
+
*
|
|
431
|
+
* Useful for GET routes or routes where input validation is handled elsewhere.
|
|
432
|
+
* The middleware validates the JSON response body and throws 500 Internal Server Error
|
|
433
|
+
* if validation fails.
|
|
434
|
+
*
|
|
435
|
+
* @template TOverrideOutput - Custom output schema type
|
|
436
|
+
* @param override - Object containing output schema
|
|
437
|
+
* @returns Middleware handler that validates response output
|
|
438
|
+
*
|
|
439
|
+
* @example GET route with output validation
|
|
440
|
+
* ```typescript
|
|
441
|
+
* router.get('/', agent.validator({ output: z.array(z.object({ id: z.string() })) }), async (c) => {
|
|
442
|
+
* // Returns array of objects - validated against schema
|
|
443
|
+
* return c.json([{ id: '123' }, { id: '456' }]);
|
|
444
|
+
* });
|
|
445
|
+
* ```
|
|
446
|
+
*/
|
|
447
|
+
<TOverrideOutput extends StandardSchemaV1>(override: {
|
|
448
|
+
output: TOverrideOutput;
|
|
449
|
+
}): Handler<
|
|
450
|
+
any,
|
|
451
|
+
any,
|
|
452
|
+
{
|
|
453
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
454
|
+
in: {};
|
|
455
|
+
out: { json: InferOutput<TOverrideOutput> };
|
|
456
|
+
}
|
|
457
|
+
>;
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Validates with custom input and optional output schemas (POST/PUT/PATCH/DELETE).
|
|
461
|
+
* Overrides the agent's schema for this specific route.
|
|
462
|
+
*
|
|
463
|
+
* @template TOverrideInput - Custom input schema type
|
|
464
|
+
* @template TOverrideOutput - Optional custom output schema type
|
|
465
|
+
* @param override - Object containing input (required) and output (optional) schemas
|
|
466
|
+
* @returns Middleware handler with type inference from custom schemas
|
|
467
|
+
*
|
|
468
|
+
* @example Custom input schema
|
|
469
|
+
* ```typescript
|
|
470
|
+
* router.post('/users', agent.validator({
|
|
471
|
+
* input: z.object({ email: z.string().email(), name: z.string() })
|
|
472
|
+
* }), async (c) => {
|
|
473
|
+
* const data = c.req.valid('json'); // { email: string, name: string }
|
|
474
|
+
* return c.json({ id: '123', ...data });
|
|
475
|
+
* });
|
|
476
|
+
* ```
|
|
477
|
+
*
|
|
478
|
+
* @example Custom input and output schemas
|
|
479
|
+
* ```typescript
|
|
480
|
+
* router.post('/convert', agent.validator({
|
|
481
|
+
* input: z.string(),
|
|
482
|
+
* output: z.number()
|
|
483
|
+
* }), async (c) => {
|
|
484
|
+
* const data = c.req.valid('json'); // string
|
|
485
|
+
* return c.json(123);
|
|
486
|
+
* });
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
<
|
|
490
|
+
TOverrideInput extends StandardSchemaV1,
|
|
491
|
+
TOverrideOutput extends StandardSchemaV1 | undefined = undefined,
|
|
492
|
+
>(override: {
|
|
493
|
+
input: TOverrideInput;
|
|
494
|
+
output?: TOverrideOutput;
|
|
495
|
+
}): Handler<
|
|
496
|
+
any,
|
|
497
|
+
any,
|
|
498
|
+
{
|
|
499
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
500
|
+
in: {};
|
|
501
|
+
out: {
|
|
502
|
+
json: InferOutput<TOverrideInput>;
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
>;
|
|
506
|
+
}
|
|
396
507
|
|
|
397
508
|
/**
|
|
398
509
|
* Agent instance type returned by createAgent().
|
|
@@ -421,7 +532,8 @@ type CreateEvalMethod<
|
|
|
421
532
|
* });
|
|
422
533
|
*
|
|
423
534
|
* // Create evals for testing
|
|
424
|
-
* const eval1 = agent.createEval({
|
|
535
|
+
* const eval1 = agent.createEval('check-positive', {
|
|
536
|
+
* description: 'Ensures result is greater than 5',
|
|
425
537
|
* handler: async (run, result) => {
|
|
426
538
|
* return result > 5; // Assert output is greater than 5
|
|
427
539
|
* }
|
|
@@ -442,12 +554,79 @@ export type Agent<
|
|
|
442
554
|
|
|
443
555
|
/**
|
|
444
556
|
* The main handler function that processes agent requests.
|
|
445
|
-
*
|
|
557
|
+
* Gets AgentContext from AsyncLocalStorage, receives validated input, returns output or stream.
|
|
446
558
|
*/
|
|
447
|
-
handler: (
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
559
|
+
handler: (...args: any[]) => any | Promise<any>;
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Creates a type-safe validation middleware for routes using StandardSchema validation.
|
|
563
|
+
*
|
|
564
|
+
* This method validates incoming JSON request bodies against the agent's **input schema**
|
|
565
|
+
* and optionally validates outgoing JSON responses against the **output schema**.
|
|
566
|
+
* Provides full TypeScript type inference for validated input data accessible via `c.req.valid('json')`.
|
|
567
|
+
*
|
|
568
|
+
* **Validation behavior:**
|
|
569
|
+
* - **Input**: Validates request JSON body, returns 400 Bad Request on failure
|
|
570
|
+
* - **Output**: Validates response JSON body (if output schema provided), throws 500 on failure
|
|
571
|
+
* - Passes validated input data to handler via `c.req.valid('json')`
|
|
572
|
+
* - Full TypeScript type inference for validated input data
|
|
573
|
+
*
|
|
574
|
+
* **Supported schema libraries:**
|
|
575
|
+
* - Zod (z.object, z.string, etc.)
|
|
576
|
+
* - Valibot (v.object, v.string, etc.)
|
|
577
|
+
* - ArkType (type({ ... }))
|
|
578
|
+
* - Any StandardSchema-compatible library
|
|
579
|
+
*
|
|
580
|
+
* **Method overloads:**
|
|
581
|
+
* - `agent.validator()` - Validates using agent's input/output schemas
|
|
582
|
+
* - `agent.validator({ output: schema })` - Output-only validation (no input validation)
|
|
583
|
+
* - `agent.validator({ input: schema })` - Custom input schema override
|
|
584
|
+
* - `agent.validator({ input: schema, output: schema })` - Both input and output validated
|
|
585
|
+
*
|
|
586
|
+
* @returns Hono middleware handler with proper type inference
|
|
587
|
+
*
|
|
588
|
+
* @example Automatic validation using agent's schema
|
|
589
|
+
* ```typescript
|
|
590
|
+
* // Agent defined with: schema: { input: z.object({ name: z.string(), age: z.number() }) }
|
|
591
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
592
|
+
* const data = c.req.valid('json'); // Fully typed: { name: string, age: number }
|
|
593
|
+
* return c.json({ greeting: `Hello ${data.name}, age ${data.age}` });
|
|
594
|
+
* });
|
|
595
|
+
* ```
|
|
596
|
+
*
|
|
597
|
+
* @example Override with custom schema per-route
|
|
598
|
+
* ```typescript
|
|
599
|
+
* router.post('/email', agent.validator({
|
|
600
|
+
* input: z.object({ email: z.string().email() })
|
|
601
|
+
* }), async (c) => {
|
|
602
|
+
* const data = c.req.valid('json'); // Typed as { email: string }
|
|
603
|
+
* return c.json({ sent: data.email });
|
|
604
|
+
* });
|
|
605
|
+
* ```
|
|
606
|
+
*
|
|
607
|
+
* @example Works with any StandardSchema library
|
|
608
|
+
* ```typescript
|
|
609
|
+
* import * as v from 'valibot';
|
|
610
|
+
*
|
|
611
|
+
* router.post('/valibot', agent.validator({
|
|
612
|
+
* input: v.object({ count: v.number() })
|
|
613
|
+
* }), async (c) => {
|
|
614
|
+
* const data = c.req.valid('json'); // Typed correctly
|
|
615
|
+
* return c.json({ count: data.count });
|
|
616
|
+
* });
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* @example Validation error response (400)
|
|
620
|
+
* ```typescript
|
|
621
|
+
* // Request: { "name": "Bob" } (missing 'age')
|
|
622
|
+
* // Response: {
|
|
623
|
+
* // "error": "Validation failed",
|
|
624
|
+
* // "message": "age: Invalid input: expected number, received undefined",
|
|
625
|
+
* // "issues": [{ "message": "...", "path": ["age"] }]
|
|
626
|
+
* // }
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
validator: AgentValidator<TInput, TOutput>;
|
|
451
630
|
|
|
452
631
|
/**
|
|
453
632
|
* Array of evaluation functions created via agent.createEval().
|
|
@@ -471,8 +650,8 @@ export type Agent<
|
|
|
471
650
|
* });
|
|
472
651
|
*
|
|
473
652
|
* // Create eval to validate output
|
|
474
|
-
* agent.createEval({
|
|
475
|
-
*
|
|
653
|
+
* agent.createEval('check-positive', {
|
|
654
|
+
* description: 'Ensures output is a positive number',
|
|
476
655
|
* handler: async (run, result) => {
|
|
477
656
|
* return result > 0; // Assert output is positive
|
|
478
657
|
* }
|
|
@@ -511,7 +690,7 @@ export type Agent<
|
|
|
511
690
|
callback: (
|
|
512
691
|
eventName: 'started',
|
|
513
692
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
514
|
-
context: AgentContext<any,
|
|
693
|
+
context: AgentContext<any, TConfig, TAppState>
|
|
515
694
|
) => Promise<void> | void
|
|
516
695
|
): void;
|
|
517
696
|
|
|
@@ -533,7 +712,7 @@ export type Agent<
|
|
|
533
712
|
callback: (
|
|
534
713
|
eventName: 'completed',
|
|
535
714
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
536
|
-
context: AgentContext<any,
|
|
715
|
+
context: AgentContext<any, TConfig, TAppState>
|
|
537
716
|
) => Promise<void> | void
|
|
538
717
|
): void;
|
|
539
718
|
|
|
@@ -555,7 +734,7 @@ export type Agent<
|
|
|
555
734
|
callback: (
|
|
556
735
|
eventName: 'errored',
|
|
557
736
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
558
|
-
context: AgentContext<any,
|
|
737
|
+
context: AgentContext<any, TConfig, TAppState>,
|
|
559
738
|
data: Error
|
|
560
739
|
) => Promise<void> | void
|
|
561
740
|
): void;
|
|
@@ -571,7 +750,7 @@ export type Agent<
|
|
|
571
750
|
callback: (
|
|
572
751
|
eventName: 'started',
|
|
573
752
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
574
|
-
context: AgentContext<any,
|
|
753
|
+
context: AgentContext<any, TConfig, TAppState>
|
|
575
754
|
) => Promise<void> | void
|
|
576
755
|
): void;
|
|
577
756
|
|
|
@@ -586,7 +765,7 @@ export type Agent<
|
|
|
586
765
|
callback: (
|
|
587
766
|
eventName: 'completed',
|
|
588
767
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
589
|
-
context: AgentContext<any,
|
|
768
|
+
context: AgentContext<any, TConfig, TAppState>
|
|
590
769
|
) => Promise<void> | void
|
|
591
770
|
): void;
|
|
592
771
|
|
|
@@ -601,7 +780,7 @@ export type Agent<
|
|
|
601
780
|
callback: (
|
|
602
781
|
eventName: 'errored',
|
|
603
782
|
agent: Agent<TInput, TOutput, TStream, TConfig, TAppState>,
|
|
604
|
-
context: AgentContext<any,
|
|
783
|
+
context: AgentContext<any, TConfig, TAppState>,
|
|
605
784
|
data: Error
|
|
606
785
|
) => Promise<void> | void
|
|
607
786
|
): void;
|
|
@@ -609,14 +788,14 @@ export type Agent<
|
|
|
609
788
|
(TOutput extends StandardSchemaV1 ? { outputSchema: TOutput } : { outputSchema?: never }) &
|
|
610
789
|
(TStream extends true ? { stream: true } : { stream?: false });
|
|
611
790
|
|
|
612
|
-
type InferSchemaInput<T> = T extends StandardSchemaV1 ?
|
|
791
|
+
type InferSchemaInput<T> = T extends StandardSchemaV1 ? InferOutput<T> : never;
|
|
613
792
|
|
|
614
793
|
type InferStreamOutput<TOutput, TStream extends boolean> = TStream extends true
|
|
615
794
|
? TOutput extends StandardSchemaV1
|
|
616
|
-
? ReadableStream<
|
|
795
|
+
? ReadableStream<InferOutput<TOutput>>
|
|
617
796
|
: ReadableStream<unknown>
|
|
618
797
|
: TOutput extends StandardSchemaV1
|
|
619
|
-
?
|
|
798
|
+
? InferOutput<TOutput>
|
|
620
799
|
: void;
|
|
621
800
|
|
|
622
801
|
type SchemaInput<TSchema> = TSchema extends { input: infer I } ? I : undefined;
|
|
@@ -630,10 +809,10 @@ type SchemaStream<TSchema> = TSchema extends { stream: infer S }
|
|
|
630
809
|
type SchemaHandlerReturn<TSchema> =
|
|
631
810
|
SchemaStream<TSchema> extends true
|
|
632
811
|
? SchemaOutput<TSchema> extends StandardSchemaV1
|
|
633
|
-
? ReadableStream<
|
|
812
|
+
? ReadableStream<InferOutput<SchemaOutput<TSchema>>>
|
|
634
813
|
: ReadableStream<unknown>
|
|
635
814
|
: SchemaOutput<TSchema> extends StandardSchemaV1
|
|
636
|
-
?
|
|
815
|
+
? InferOutput<SchemaOutput<TSchema>>
|
|
637
816
|
: void;
|
|
638
817
|
|
|
639
818
|
// Handler signature based on schema + setup result (no self-reference)
|
|
@@ -641,21 +820,37 @@ type AgentHandlerFromConfig<TSchema, TSetupReturn, TAppState = AppState> =
|
|
|
641
820
|
SchemaInput<TSchema> extends infer I
|
|
642
821
|
? I extends StandardSchemaV1
|
|
643
822
|
? (
|
|
644
|
-
ctx: AgentContext<any,
|
|
645
|
-
input:
|
|
823
|
+
ctx: AgentContext<any, TSetupReturn, TAppState>,
|
|
824
|
+
input: InferOutput<I>
|
|
646
825
|
) => Promise<SchemaHandlerReturn<TSchema>> | SchemaHandlerReturn<TSchema>
|
|
647
826
|
: (
|
|
648
|
-
ctx: AgentContext<any,
|
|
827
|
+
ctx: AgentContext<any, TSetupReturn, TAppState>
|
|
649
828
|
) => Promise<SchemaHandlerReturn<TSchema>> | SchemaHandlerReturn<TSchema>
|
|
650
829
|
: (
|
|
651
|
-
ctx: AgentContext<any,
|
|
830
|
+
ctx: AgentContext<any, TSetupReturn, TAppState>
|
|
652
831
|
) => Promise<SchemaHandlerReturn<TSchema>> | SchemaHandlerReturn<TSchema>;
|
|
653
832
|
|
|
654
833
|
/**
|
|
655
834
|
* Configuration object for creating an agent with automatic type inference.
|
|
656
835
|
*
|
|
836
|
+
* Passed as the second parameter to createAgent(name, config).
|
|
837
|
+
*
|
|
657
838
|
* @template TSchema - Schema definition object containing optional input, output, and stream properties
|
|
658
839
|
* @template TConfig - Function type that returns agent-specific configuration from setup
|
|
840
|
+
*
|
|
841
|
+
* @example
|
|
842
|
+
* ```typescript
|
|
843
|
+
* const agent = createAgent('greeting', {
|
|
844
|
+
* description: 'Generates personalized greetings',
|
|
845
|
+
* schema: {
|
|
846
|
+
* input: z.object({ name: z.string(), age: z.number() }),
|
|
847
|
+
* output: z.string()
|
|
848
|
+
* },
|
|
849
|
+
* handler: async (ctx, { name, age }) => {
|
|
850
|
+
* return `Hello, ${name}! You are ${age} years old.`;
|
|
851
|
+
* }
|
|
852
|
+
* });
|
|
853
|
+
* ```
|
|
659
854
|
*/
|
|
660
855
|
export interface CreateAgentConfig<
|
|
661
856
|
TSchema extends
|
|
@@ -682,17 +877,22 @@ export interface CreateAgentConfig<
|
|
|
682
877
|
schema?: TSchema;
|
|
683
878
|
|
|
684
879
|
/**
|
|
685
|
-
*
|
|
880
|
+
* Optional description of what this agent does, visible in the Agentuity platform.
|
|
686
881
|
*
|
|
687
882
|
* @example
|
|
688
883
|
* ```typescript
|
|
689
|
-
*
|
|
690
|
-
* name: 'Greeting Agent',
|
|
691
|
-
* description: 'Returns personalized greetings'
|
|
692
|
-
* }
|
|
884
|
+
* description: 'Returns personalized greetings'
|
|
693
885
|
* ```
|
|
694
886
|
*/
|
|
695
|
-
|
|
887
|
+
description?: string;
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Optional metadata object (typically injected by build plugin during compilation).
|
|
891
|
+
* Contains agent identification and versioning information.
|
|
892
|
+
*
|
|
893
|
+
* @internal - Usually populated by build tooling, not manually set
|
|
894
|
+
*/
|
|
895
|
+
metadata?: Partial<AgentMetadata>;
|
|
696
896
|
|
|
697
897
|
/**
|
|
698
898
|
* Optional async function called once on app startup to initialize agent-specific resources.
|
|
@@ -756,17 +956,187 @@ export interface CreateAgentConfig<
|
|
|
756
956
|
) => Promise<void> | void;
|
|
757
957
|
}
|
|
758
958
|
|
|
959
|
+
/**
|
|
960
|
+
* The public interface returned by createAgent().
|
|
961
|
+
* Provides methods to run the agent, create evaluations, and manage event listeners.
|
|
962
|
+
*
|
|
963
|
+
* @template TInput - Input schema type (StandardSchemaV1 or undefined if no input)
|
|
964
|
+
* @template TOutput - Output schema type (StandardSchemaV1 or undefined if no output)
|
|
965
|
+
* @template TStream - Whether the agent returns a stream (true/false)
|
|
966
|
+
*
|
|
967
|
+
* @example
|
|
968
|
+
* ```typescript
|
|
969
|
+
* const agent = createAgent('greeting', {
|
|
970
|
+
* schema: {
|
|
971
|
+
* input: z.object({ name: z.string() }),
|
|
972
|
+
* output: z.string()
|
|
973
|
+
* },
|
|
974
|
+
* handler: async (ctx, { name }) => `Hello, ${name}!`
|
|
975
|
+
* });
|
|
976
|
+
*
|
|
977
|
+
* // Run the agent
|
|
978
|
+
* const result = await agent.run({ name: 'Alice' });
|
|
979
|
+
*
|
|
980
|
+
* // Create evaluation
|
|
981
|
+
* const evalDef = agent.createEval('greeting-accuracy', {
|
|
982
|
+
* description: 'Checks if greeting includes the user name',
|
|
983
|
+
* handler: async (ctx, input, output) => {
|
|
984
|
+
* return { score: output.includes(input.name) ? 1 : 0 };
|
|
985
|
+
* }
|
|
986
|
+
* });
|
|
987
|
+
*
|
|
988
|
+
* // Listen to events
|
|
989
|
+
* agent.addEventListener('completed', async (eventName, agent, context) => {
|
|
990
|
+
* console.log('Agent completed successfully');
|
|
991
|
+
* });
|
|
992
|
+
* ```
|
|
993
|
+
*/
|
|
759
994
|
export interface AgentRunner<
|
|
760
995
|
TInput extends StandardSchemaV1 | undefined = any,
|
|
761
996
|
TOutput extends StandardSchemaV1 | undefined = any,
|
|
762
997
|
TStream extends boolean = false,
|
|
763
998
|
> {
|
|
999
|
+
/** Agent metadata (id, name, description, etc.) */
|
|
764
1000
|
metadata: AgentMetadata;
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Execute the agent with validated input.
|
|
1004
|
+
* If agent has no input schema, call with no arguments.
|
|
1005
|
+
* If agent has input schema, pass validated input object.
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* ```typescript
|
|
1009
|
+
* // Agent with input
|
|
1010
|
+
* const result = await agent.run({ name: 'Alice' });
|
|
1011
|
+
*
|
|
1012
|
+
* // Agent without input
|
|
1013
|
+
* const result = await agent.run();
|
|
1014
|
+
* ```
|
|
1015
|
+
*/
|
|
765
1016
|
run: undefined extends TInput
|
|
766
1017
|
? () => Promise<InferStreamOutput<Exclude<TOutput, undefined>, TStream>>
|
|
767
1018
|
: (
|
|
768
1019
|
input: InferSchemaInput<Exclude<TInput, undefined>>
|
|
769
1020
|
) => Promise<InferStreamOutput<Exclude<TOutput, undefined>, TStream>>;
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Create Hono validator middleware for this agent.
|
|
1024
|
+
* Automatically validates request input against the agent's schema.
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```typescript
|
|
1028
|
+
* import myAgent from './my-agent';
|
|
1029
|
+
* router.post('/', myAgent.validator(), async (c) => {
|
|
1030
|
+
* const data = c.req.valid('json'); // Fully typed!
|
|
1031
|
+
* return c.json(await myAgent.run(data));
|
|
1032
|
+
* });
|
|
1033
|
+
* ```
|
|
1034
|
+
*/
|
|
1035
|
+
validator: AgentValidator<TInput, TOutput>;
|
|
1036
|
+
|
|
1037
|
+
/** Input schema (if defined) */
|
|
1038
|
+
inputSchema?: TInput;
|
|
1039
|
+
|
|
1040
|
+
/** Output schema (if defined) */
|
|
1041
|
+
outputSchema?: TOutput;
|
|
1042
|
+
|
|
1043
|
+
/** Whether agent returns a stream */
|
|
1044
|
+
stream?: TStream;
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Create an evaluation for this agent.
|
|
1048
|
+
* Evaluations run automatically after the agent completes.
|
|
1049
|
+
*
|
|
1050
|
+
* @example
|
|
1051
|
+
* ```typescript
|
|
1052
|
+
* const accuracyEval = agent.createEval('accuracy', {
|
|
1053
|
+
* description: 'Validates output length is non-zero',
|
|
1054
|
+
* handler: async (ctx, input, output) => ({
|
|
1055
|
+
* score: output.length > 0 ? 1 : 0,
|
|
1056
|
+
* metadata: { outputLength: output.length }
|
|
1057
|
+
* })
|
|
1058
|
+
* });
|
|
1059
|
+
* ```
|
|
1060
|
+
*/
|
|
1061
|
+
createEval: CreateEvalMethod<TInput, TOutput>;
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Add event listener for 'started' or 'completed' events.
|
|
1065
|
+
* Listeners fire sequentially in the order they were added.
|
|
1066
|
+
*
|
|
1067
|
+
* @param eventName - 'started' or 'completed'
|
|
1068
|
+
* @param callback - Function to call when event fires
|
|
1069
|
+
*
|
|
1070
|
+
* @example
|
|
1071
|
+
* ```typescript
|
|
1072
|
+
* agent.addEventListener('started', async (eventName, agent, context) => {
|
|
1073
|
+
* context.logger.info('Agent execution started');
|
|
1074
|
+
* });
|
|
1075
|
+
* ```
|
|
1076
|
+
*/
|
|
1077
|
+
addEventListener(
|
|
1078
|
+
eventName: 'started' | 'completed',
|
|
1079
|
+
callback: (
|
|
1080
|
+
eventName: 'started' | 'completed',
|
|
1081
|
+
agent: Agent<TInput, TOutput, TStream, any, any>,
|
|
1082
|
+
context: AgentContext<any, any, any>
|
|
1083
|
+
) => Promise<void> | void
|
|
1084
|
+
): void;
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Add event listener for 'errored' event.
|
|
1088
|
+
* Fires when agent handler throws an error.
|
|
1089
|
+
*
|
|
1090
|
+
* @param eventName - 'errored'
|
|
1091
|
+
* @param callback - Function to call when error occurs
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* ```typescript
|
|
1095
|
+
* agent.addEventListener('errored', async (eventName, agent, context, error) => {
|
|
1096
|
+
* context.logger.error('Agent failed', { error: error.message });
|
|
1097
|
+
* });
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
addEventListener(
|
|
1101
|
+
eventName: 'errored',
|
|
1102
|
+
callback: (
|
|
1103
|
+
eventName: 'errored',
|
|
1104
|
+
agent: Agent<TInput, TOutput, TStream, any, any>,
|
|
1105
|
+
context: AgentContext<any, any, any>,
|
|
1106
|
+
error: Error
|
|
1107
|
+
) => Promise<void> | void
|
|
1108
|
+
): void;
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Remove event listener for 'started' or 'completed' events.
|
|
1112
|
+
*
|
|
1113
|
+
* @param eventName - 'started' or 'completed'
|
|
1114
|
+
* @param callback - The same callback function that was added
|
|
1115
|
+
*/
|
|
1116
|
+
removeEventListener(
|
|
1117
|
+
eventName: 'started' | 'completed',
|
|
1118
|
+
callback: (
|
|
1119
|
+
eventName: 'started' | 'completed',
|
|
1120
|
+
agent: Agent<TInput, TOutput, TStream, any, any>,
|
|
1121
|
+
context: AgentContext<any, any, any>
|
|
1122
|
+
) => Promise<void> | void
|
|
1123
|
+
): void;
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Remove event listener for 'errored' event.
|
|
1127
|
+
*
|
|
1128
|
+
* @param eventName - 'errored'
|
|
1129
|
+
* @param callback - The same callback function that was added
|
|
1130
|
+
*/
|
|
1131
|
+
removeEventListener(
|
|
1132
|
+
eventName: 'errored',
|
|
1133
|
+
callback: (
|
|
1134
|
+
eventName: 'errored',
|
|
1135
|
+
agent: Agent<TInput, TOutput, TStream, any, any>,
|
|
1136
|
+
context: AgentContext<any, any, any>,
|
|
1137
|
+
error: Error
|
|
1138
|
+
) => Promise<void> | void
|
|
1139
|
+
): void;
|
|
770
1140
|
}
|
|
771
1141
|
|
|
772
1142
|
// Will be populated at runtime with strongly typed agents
|
|
@@ -781,26 +1151,49 @@ const agentEventListeners = new WeakMap<
|
|
|
781
1151
|
// Map to store agent configs returned from setup (keyed by agent name)
|
|
782
1152
|
const agentConfigs = new Map<string, unknown>();
|
|
783
1153
|
|
|
1154
|
+
/**
|
|
1155
|
+
* Get the global runtime state (for production use).
|
|
1156
|
+
* In tests, use TestAgentContext which has isolated runtime state.
|
|
1157
|
+
*/
|
|
1158
|
+
export function getGlobalRuntimeState(): AgentRuntimeState {
|
|
1159
|
+
return {
|
|
1160
|
+
agents,
|
|
1161
|
+
agentConfigs,
|
|
1162
|
+
agentEventListeners,
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Get the runtime state from an AgentContext.
|
|
1168
|
+
* @internal
|
|
1169
|
+
*/
|
|
1170
|
+
export function getAgentRuntime(ctx: AgentContext<any, any, any>): AgentRuntimeState {
|
|
1171
|
+
return ctx[AGENT_RUNTIME];
|
|
1172
|
+
}
|
|
1173
|
+
|
|
784
1174
|
// Helper to fire event listeners sequentially, abort on first error
|
|
785
1175
|
async function fireAgentEvent(
|
|
1176
|
+
runtime: AgentRuntimeState,
|
|
786
1177
|
agent: Agent<any, any, any, any, any>,
|
|
787
1178
|
eventName: 'started' | 'completed',
|
|
788
|
-
context: AgentContext<any, any, any
|
|
1179
|
+
context: AgentContext<any, any, any>
|
|
789
1180
|
): Promise<void>;
|
|
790
1181
|
async function fireAgentEvent(
|
|
1182
|
+
runtime: AgentRuntimeState,
|
|
791
1183
|
agent: Agent<any, any, any, any, any>,
|
|
792
1184
|
eventName: 'errored',
|
|
793
|
-
context: AgentContext<any, any, any
|
|
1185
|
+
context: AgentContext<any, any, any>,
|
|
794
1186
|
data: Error
|
|
795
1187
|
): Promise<void>;
|
|
796
1188
|
async function fireAgentEvent(
|
|
1189
|
+
runtime: AgentRuntimeState,
|
|
797
1190
|
agent: Agent<any, any, any, any, any>,
|
|
798
1191
|
eventName: AgentEventName,
|
|
799
|
-
context: AgentContext<any, any, any
|
|
1192
|
+
context: AgentContext<any, any, any>,
|
|
800
1193
|
data?: Error
|
|
801
1194
|
): Promise<void> {
|
|
802
1195
|
// Fire agent-level listeners
|
|
803
|
-
const listeners = agentEventListeners.get(agent);
|
|
1196
|
+
const listeners = runtime.agentEventListeners.get(agent);
|
|
804
1197
|
if (listeners) {
|
|
805
1198
|
const callbacks = listeners.get(eventName);
|
|
806
1199
|
if (callbacks && callbacks.size > 0) {
|
|
@@ -895,17 +1288,22 @@ export interface CreateAgentConfigExplicit<
|
|
|
895
1288
|
};
|
|
896
1289
|
|
|
897
1290
|
/**
|
|
898
|
-
*
|
|
1291
|
+
* Optional description of what this agent does.
|
|
899
1292
|
*
|
|
900
1293
|
* @example
|
|
901
1294
|
* ```typescript
|
|
902
|
-
*
|
|
903
|
-
* name: 'My Agent',
|
|
904
|
-
* description: 'Does something useful'
|
|
905
|
-
* }
|
|
1295
|
+
* description: 'Does something useful'
|
|
906
1296
|
* ```
|
|
907
1297
|
*/
|
|
908
|
-
|
|
1298
|
+
description?: string;
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Optional metadata object (typically injected by build plugin during compilation).
|
|
1302
|
+
* Contains agent identification and versioning information.
|
|
1303
|
+
*
|
|
1304
|
+
* @internal - Usually populated by build tooling, not manually set
|
|
1305
|
+
*/
|
|
1306
|
+
metadata?: Partial<AgentMetadata>;
|
|
909
1307
|
|
|
910
1308
|
/**
|
|
911
1309
|
* Optional setup function receiving app state, returns agent config.
|
|
@@ -955,43 +1353,39 @@ export interface CreateAgentConfigExplicit<
|
|
|
955
1353
|
? TStream extends true
|
|
956
1354
|
? TOutput extends StandardSchemaV1
|
|
957
1355
|
? (
|
|
958
|
-
c: AgentContext<any,
|
|
959
|
-
input:
|
|
1356
|
+
c: AgentContext<any, TConfig, TAppState>,
|
|
1357
|
+
input: InferOutput<TInput>
|
|
960
1358
|
) =>
|
|
961
|
-
| Promise<ReadableStream<
|
|
962
|
-
| ReadableStream<
|
|
1359
|
+
| Promise<ReadableStream<InferOutput<TOutput>>>
|
|
1360
|
+
| ReadableStream<InferOutput<TOutput>>
|
|
963
1361
|
: (
|
|
964
|
-
c: AgentContext<any,
|
|
965
|
-
input:
|
|
1362
|
+
c: AgentContext<any, TConfig, TAppState>,
|
|
1363
|
+
input: InferOutput<TInput>
|
|
966
1364
|
) => Promise<ReadableStream<unknown>> | ReadableStream<unknown>
|
|
967
1365
|
: TOutput extends StandardSchemaV1
|
|
968
1366
|
? (
|
|
969
|
-
c: AgentContext<any,
|
|
970
|
-
input:
|
|
971
|
-
) =>
|
|
972
|
-
| Promise<StandardSchemaV1.InferOutput<TOutput>>
|
|
973
|
-
| StandardSchemaV1.InferOutput<TOutput>
|
|
1367
|
+
c: AgentContext<any, TConfig, TAppState>,
|
|
1368
|
+
input: InferOutput<TInput>
|
|
1369
|
+
) => Promise<InferOutput<TOutput>> | InferOutput<TOutput>
|
|
974
1370
|
: (
|
|
975
|
-
c: AgentContext<any,
|
|
976
|
-
input:
|
|
1371
|
+
c: AgentContext<any, TConfig, TAppState>,
|
|
1372
|
+
input: InferOutput<TInput>
|
|
977
1373
|
) => Promise<void> | void
|
|
978
1374
|
: TStream extends true
|
|
979
1375
|
? TOutput extends StandardSchemaV1
|
|
980
1376
|
? (
|
|
981
|
-
c: AgentContext<any,
|
|
1377
|
+
c: AgentContext<any, TConfig, TAppState>
|
|
982
1378
|
) =>
|
|
983
|
-
| Promise<ReadableStream<
|
|
984
|
-
| ReadableStream<
|
|
1379
|
+
| Promise<ReadableStream<InferOutput<TOutput>>>
|
|
1380
|
+
| ReadableStream<InferOutput<TOutput>>
|
|
985
1381
|
: (
|
|
986
|
-
c: AgentContext<any,
|
|
1382
|
+
c: AgentContext<any, TConfig, TAppState>
|
|
987
1383
|
) => Promise<ReadableStream<unknown>> | ReadableStream<unknown>
|
|
988
1384
|
: TOutput extends StandardSchemaV1
|
|
989
1385
|
? (
|
|
990
|
-
c: AgentContext<any,
|
|
991
|
-
) =>
|
|
992
|
-
|
|
993
|
-
| StandardSchemaV1.InferOutput<TOutput>
|
|
994
|
-
: (c: AgentContext<any, any, any, TConfig, TAppState>) => Promise<void> | void;
|
|
1386
|
+
c: AgentContext<any, TConfig, TAppState>
|
|
1387
|
+
) => Promise<InferOutput<TOutput>> | InferOutput<TOutput>
|
|
1388
|
+
: (c: AgentContext<any, TConfig, TAppState>) => Promise<void> | void;
|
|
995
1389
|
}
|
|
996
1390
|
|
|
997
1391
|
/**
|
|
@@ -1002,17 +1396,15 @@ export interface CreateAgentConfigExplicit<
|
|
|
1002
1396
|
* @template TSchema - Schema definition object containing optional input, output, and stream properties
|
|
1003
1397
|
* @template TConfig - Function type that returns agent-specific configuration from setup
|
|
1004
1398
|
*
|
|
1399
|
+
* @param name - Unique agent name (must be unique within the project)
|
|
1005
1400
|
* @param config - Agent configuration object
|
|
1006
1401
|
*
|
|
1007
|
-
* @returns
|
|
1402
|
+
* @returns AgentRunner with a run method for executing the agent
|
|
1008
1403
|
*
|
|
1009
1404
|
* @example
|
|
1010
1405
|
* ```typescript
|
|
1011
|
-
* const agent = createAgent({
|
|
1012
|
-
*
|
|
1013
|
-
* name: 'Greeting Agent',
|
|
1014
|
-
* description: 'Returns personalized greetings'
|
|
1015
|
-
* },
|
|
1406
|
+
* const agent = createAgent('greeting-agent', {
|
|
1407
|
+
* description: 'Returns personalized greetings',
|
|
1016
1408
|
* schema: {
|
|
1017
1409
|
* input: z.object({ name: z.string(), age: z.number() }),
|
|
1018
1410
|
* output: z.string()
|
|
@@ -1022,6 +1414,9 @@ export interface CreateAgentConfigExplicit<
|
|
|
1022
1414
|
* return `Hello, ${name}! You are ${age} years old.`;
|
|
1023
1415
|
* }
|
|
1024
1416
|
* });
|
|
1417
|
+
*
|
|
1418
|
+
* // Call the agent directly
|
|
1419
|
+
* const result = await agent.run({ name: 'Alice', age: 30 });
|
|
1025
1420
|
* ```
|
|
1026
1421
|
*/
|
|
1027
1422
|
export function createAgent<
|
|
@@ -1034,14 +1429,9 @@ export function createAgent<
|
|
|
1034
1429
|
| undefined = undefined,
|
|
1035
1430
|
TConfig extends (app: AppState) => any = any,
|
|
1036
1431
|
>(
|
|
1432
|
+
name: string,
|
|
1037
1433
|
config: CreateAgentConfig<TSchema, TConfig>
|
|
1038
|
-
):
|
|
1039
|
-
SchemaInput<TSchema>,
|
|
1040
|
-
SchemaOutput<TSchema>,
|
|
1041
|
-
SchemaStream<TSchema>,
|
|
1042
|
-
TConfig extends (app: AppState) => infer R ? Awaited<R> : undefined,
|
|
1043
|
-
AppState
|
|
1044
|
-
>;
|
|
1434
|
+
): AgentRunner<SchemaInput<TSchema>, SchemaOutput<TSchema>, SchemaStream<TSchema>>;
|
|
1045
1435
|
|
|
1046
1436
|
/**
|
|
1047
1437
|
* Creates an agent with explicit generic type parameters.
|
|
@@ -1054,9 +1444,10 @@ export function createAgent<
|
|
|
1054
1444
|
* @template TConfig - Type returned by setup function
|
|
1055
1445
|
* @template TAppState - Custom app state type from createApp
|
|
1056
1446
|
*
|
|
1447
|
+
* @param name - Unique agent name (must be unique within the project)
|
|
1057
1448
|
* @param config - Agent configuration object
|
|
1058
1449
|
*
|
|
1059
|
-
* @returns
|
|
1450
|
+
* @returns AgentRunner with explicit types and a run method
|
|
1060
1451
|
*
|
|
1061
1452
|
* @example
|
|
1062
1453
|
* ```typescript
|
|
@@ -1066,11 +1457,8 @@ export function createAgent<
|
|
|
1066
1457
|
* const agent = createAgent<
|
|
1067
1458
|
* z.ZodObject<any>, // TInput
|
|
1068
1459
|
* z.ZodString, // TOutput
|
|
1069
|
-
* false
|
|
1070
|
-
*
|
|
1071
|
-
* MyAppState // TAppState
|
|
1072
|
-
* >({
|
|
1073
|
-
* metadata: { name: 'Custom Agent' },
|
|
1460
|
+
* false // TStream
|
|
1461
|
+
* >('custom-agent', {
|
|
1074
1462
|
* setup: async (app) => ({ cache: new Map() }),
|
|
1075
1463
|
* handler: async (ctx, input) => {
|
|
1076
1464
|
* const db = ctx.app.db;
|
|
@@ -1087,8 +1475,9 @@ export function createAgent<
|
|
|
1087
1475
|
TConfig = unknown,
|
|
1088
1476
|
TAppState = AppState,
|
|
1089
1477
|
>(
|
|
1478
|
+
name: string,
|
|
1090
1479
|
config: CreateAgentConfigExplicit<TInput, TOutput, TStream, TConfig, TAppState>
|
|
1091
|
-
):
|
|
1480
|
+
): AgentRunner<TInput, TOutput, TStream>;
|
|
1092
1481
|
|
|
1093
1482
|
// Implementation
|
|
1094
1483
|
export function createAgent<
|
|
@@ -1098,8 +1487,9 @@ export function createAgent<
|
|
|
1098
1487
|
TConfig = unknown,
|
|
1099
1488
|
TAppState = AppState,
|
|
1100
1489
|
>(
|
|
1490
|
+
name: string,
|
|
1101
1491
|
config: CreateAgentConfigExplicit<TInput, TOutput, TStream, TConfig, TAppState>
|
|
1102
|
-
):
|
|
1492
|
+
): AgentRunner<TInput, TOutput, TStream> {
|
|
1103
1493
|
const inputSchema = config.schema?.input;
|
|
1104
1494
|
const outputSchema = config.schema?.output;
|
|
1105
1495
|
|
|
@@ -1107,7 +1497,7 @@ export function createAgent<
|
|
|
1107
1497
|
// Evals should only be added via agent.createEval() after agent creation
|
|
1108
1498
|
const evalsArray: Eval[] = [];
|
|
1109
1499
|
|
|
1110
|
-
const handler = async (
|
|
1500
|
+
const handler = async (input?: any) => {
|
|
1111
1501
|
let validatedInput: any = undefined;
|
|
1112
1502
|
|
|
1113
1503
|
if (inputSchema) {
|
|
@@ -1121,17 +1511,16 @@ export function createAgent<
|
|
|
1121
1511
|
validatedInput = inputResult.value;
|
|
1122
1512
|
}
|
|
1123
1513
|
|
|
1124
|
-
const agentCtx = getAgentContext() as AgentContext<any,
|
|
1514
|
+
const agentCtx = getAgentContext() as AgentContext<any, TConfig, TAppState>;
|
|
1125
1515
|
|
|
1126
|
-
//
|
|
1127
|
-
|
|
1128
|
-
const agentName = agentCtx.agentName;
|
|
1129
|
-
const registeredAgent = agentName ? agents.get(agentName) : undefined;
|
|
1516
|
+
// Store current agent for telemetry (using Symbol to keep it internal)
|
|
1517
|
+
(agentCtx as any)[CURRENT_AGENT] = agent;
|
|
1130
1518
|
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1519
|
+
// Get the agent instance from the runtime state to fire events
|
|
1520
|
+
const runtime = getAgentRuntime(agentCtx);
|
|
1521
|
+
|
|
1522
|
+
// Fire 'started' event
|
|
1523
|
+
await fireAgentEvent(runtime, agent as Agent, 'started', agentCtx);
|
|
1135
1524
|
|
|
1136
1525
|
try {
|
|
1137
1526
|
const result = inputSchema
|
|
@@ -1139,7 +1528,8 @@ export function createAgent<
|
|
|
1139
1528
|
: await (config.handler as any)(agentCtx);
|
|
1140
1529
|
|
|
1141
1530
|
let validatedOutput: any = result;
|
|
1142
|
-
|
|
1531
|
+
// Skip output validation for streaming agents (they return ReadableStream)
|
|
1532
|
+
if (outputSchema && !config.schema?.stream) {
|
|
1143
1533
|
const outputResult = await outputSchema['~standard'].validate(result);
|
|
1144
1534
|
if (outputResult.issues) {
|
|
1145
1535
|
throw new ValidationError({
|
|
@@ -1155,50 +1545,44 @@ export function createAgent<
|
|
|
1155
1545
|
agentCtx.state.set('_evalOutput', validatedOutput);
|
|
1156
1546
|
|
|
1157
1547
|
// Fire 'completed' event - evals will run via event listener
|
|
1158
|
-
|
|
1159
|
-
await fireAgentEvent(registeredAgent, 'completed', agentCtx);
|
|
1160
|
-
}
|
|
1548
|
+
await fireAgentEvent(runtime, agent as Agent, 'completed', agentCtx);
|
|
1161
1549
|
|
|
1162
1550
|
return validatedOutput;
|
|
1163
1551
|
} catch (error) {
|
|
1164
1552
|
// Fire 'errored' event
|
|
1165
|
-
|
|
1166
|
-
await fireAgentEvent(registeredAgent, 'errored', agentCtx, error as Error);
|
|
1167
|
-
}
|
|
1553
|
+
await fireAgentEvent(runtime, agent as Agent, 'errored', agentCtx, error as Error);
|
|
1168
1554
|
throw error;
|
|
1169
1555
|
}
|
|
1170
1556
|
};
|
|
1171
1557
|
|
|
1172
1558
|
// Infer input/output types from agent schema
|
|
1173
|
-
type AgentInput = TInput extends StandardSchemaV1
|
|
1174
|
-
|
|
1175
|
-
: undefined;
|
|
1176
|
-
type AgentOutput = TOutput extends StandardSchemaV1
|
|
1177
|
-
? StandardSchemaV1.InferOutput<TOutput>
|
|
1178
|
-
: undefined;
|
|
1559
|
+
type AgentInput = TInput extends StandardSchemaV1 ? InferOutput<TInput> : undefined;
|
|
1560
|
+
type AgentOutput = TOutput extends StandardSchemaV1 ? InferOutput<TOutput> : undefined;
|
|
1179
1561
|
|
|
1180
1562
|
// Create createEval method that infers types from agent and automatically adds to agent
|
|
1181
|
-
const createEval = (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1563
|
+
const createEval = (
|
|
1564
|
+
evalName: string,
|
|
1565
|
+
evalConfig: {
|
|
1566
|
+
description?: string;
|
|
1567
|
+
handler: EvalFunction<AgentInput, AgentOutput>;
|
|
1568
|
+
}
|
|
1569
|
+
): Eval<TInput, TOutput> => {
|
|
1186
1570
|
// Trace log to verify evals file is imported
|
|
1187
1571
|
internal.debug(
|
|
1188
|
-
`createEval called for agent "${
|
|
1572
|
+
`createEval called for agent "${name || 'unknown'}": registering eval "${evalName}"`
|
|
1189
1573
|
);
|
|
1190
1574
|
|
|
1191
|
-
// Get filename (
|
|
1192
|
-
const filename =
|
|
1575
|
+
// Get filename (set by bundler)
|
|
1576
|
+
const filename = '';
|
|
1193
1577
|
|
|
1194
1578
|
// Use name as identifier for consistency (same as agents)
|
|
1195
1579
|
const identifier = evalName;
|
|
1196
1580
|
|
|
1197
1581
|
// Use build-time injected id/version if available, otherwise generate at runtime
|
|
1198
1582
|
// Build-time injection happens via bundler AST transformation
|
|
1199
|
-
let evalId
|
|
1200
|
-
let stableEvalId
|
|
1201
|
-
let version
|
|
1583
|
+
let evalId: string | undefined;
|
|
1584
|
+
let stableEvalId: string | undefined;
|
|
1585
|
+
let version: string | undefined;
|
|
1202
1586
|
|
|
1203
1587
|
// Generate version from available metadata if not provided (deterministic hash)
|
|
1204
1588
|
// At build-time, version is hash of file contents; at runtime we use metadata
|
|
@@ -1239,8 +1623,8 @@ export function createAgent<
|
|
|
1239
1623
|
evalId: stableEvalId,
|
|
1240
1624
|
version,
|
|
1241
1625
|
identifier,
|
|
1242
|
-
name:
|
|
1243
|
-
description: evalConfig.
|
|
1626
|
+
name: evalName,
|
|
1627
|
+
description: evalConfig.description || '',
|
|
1244
1628
|
filename,
|
|
1245
1629
|
},
|
|
1246
1630
|
handler: evalConfig.handler,
|
|
@@ -1257,15 +1641,31 @@ export function createAgent<
|
|
|
1257
1641
|
// Automatically add eval to agent's evals array
|
|
1258
1642
|
evalsArray.push(evalType);
|
|
1259
1643
|
internal.debug(
|
|
1260
|
-
`Added eval "${evalName}" to agent "${
|
|
1644
|
+
`Added eval "${evalName}" to agent "${name || 'unknown'}". Total evals: ${evalsArray.length}`
|
|
1261
1645
|
);
|
|
1262
1646
|
|
|
1263
1647
|
return evalType as Eval<TInput, TOutput>;
|
|
1264
1648
|
};
|
|
1265
1649
|
|
|
1650
|
+
// Build metadata - merge user-provided metadata with defaults
|
|
1651
|
+
// The build plugin injects metadata via config.metadata during AST transformation
|
|
1652
|
+
const metadata: Partial<AgentMetadata> = {
|
|
1653
|
+
// Defaults (used when running without build, e.g., dev mode)
|
|
1654
|
+
name,
|
|
1655
|
+
description: config.description,
|
|
1656
|
+
id: '',
|
|
1657
|
+
agentId: '',
|
|
1658
|
+
filename: '',
|
|
1659
|
+
version: '',
|
|
1660
|
+
inputSchemaCode: '',
|
|
1661
|
+
outputSchemaCode: '',
|
|
1662
|
+
// Merge in build-time injected metadata (overrides defaults)
|
|
1663
|
+
...config.metadata,
|
|
1664
|
+
};
|
|
1665
|
+
|
|
1266
1666
|
const agent: any = {
|
|
1267
1667
|
handler,
|
|
1268
|
-
metadata
|
|
1668
|
+
metadata,
|
|
1269
1669
|
evals: evalsArray,
|
|
1270
1670
|
createEval,
|
|
1271
1671
|
setup: config.setup,
|
|
@@ -1291,14 +1691,12 @@ export function createAgent<
|
|
|
1291
1691
|
|
|
1292
1692
|
// Automatically add event listener for 'completed' event to run evals
|
|
1293
1693
|
(agent as Agent).addEventListener('completed', async (_event, _agent, ctx) => {
|
|
1294
|
-
//
|
|
1694
|
+
// Use the agent instance passed to event listener to access its evals array
|
|
1295
1695
|
// This ensures we get evals that were added via agent.createEval() after agent creation
|
|
1296
|
-
const
|
|
1297
|
-
const registeredAgent = agentName ? agents.get(agentName) : undefined;
|
|
1298
|
-
const agentEvals = registeredAgent?.evals || evalsArray;
|
|
1696
|
+
const agentEvals = _agent?.evals || evalsArray;
|
|
1299
1697
|
|
|
1300
1698
|
internal.debug(
|
|
1301
|
-
`Checking evals:
|
|
1699
|
+
`Checking evals: agent=${_agent.metadata?.name}, evalsArray.length=${evalsArray?.length || 0}, agent.evals.length=${_agent?.evals?.length || 0}`
|
|
1302
1700
|
);
|
|
1303
1701
|
|
|
1304
1702
|
if (agentEvals && agentEvals.length > 0) {
|
|
@@ -1536,7 +1934,159 @@ export function createAgent<
|
|
|
1536
1934
|
agent.stream = config.schema.stream;
|
|
1537
1935
|
}
|
|
1538
1936
|
|
|
1539
|
-
|
|
1937
|
+
// Add validator method with overloads
|
|
1938
|
+
agent.validator = ((override?: any) => {
|
|
1939
|
+
const effectiveInputSchema = override?.input ?? inputSchema;
|
|
1940
|
+
const effectiveOutputSchema = override?.output ?? outputSchema;
|
|
1941
|
+
|
|
1942
|
+
// Helper to build the standard Hono input validator so types flow
|
|
1943
|
+
const buildInputValidator = (schema?: StandardSchemaV1) =>
|
|
1944
|
+
validator('json', async (value, c) => {
|
|
1945
|
+
if (schema) {
|
|
1946
|
+
const result = await validateSchema(schema, value);
|
|
1947
|
+
if (!result.success) {
|
|
1948
|
+
return c.json(
|
|
1949
|
+
{
|
|
1950
|
+
error: 'Validation failed',
|
|
1951
|
+
message: formatValidationIssues(result.issues),
|
|
1952
|
+
issues: result.issues,
|
|
1953
|
+
},
|
|
1954
|
+
400
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
return result.data;
|
|
1958
|
+
}
|
|
1959
|
+
return value;
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
// If no output schema, preserve existing behavior: pure input validation
|
|
1963
|
+
if (!effectiveOutputSchema) {
|
|
1964
|
+
return buildInputValidator(effectiveInputSchema);
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// Output validation middleware (runs after handler)
|
|
1968
|
+
const outputValidator: MiddlewareHandler = async (c, next) => {
|
|
1969
|
+
await next();
|
|
1970
|
+
|
|
1971
|
+
const res = c.res;
|
|
1972
|
+
if (!res) return;
|
|
1973
|
+
|
|
1974
|
+
// Skip output validation for streaming agents
|
|
1975
|
+
if (config.schema?.stream) {
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
// Only validate JSON responses
|
|
1980
|
+
const contentType = res.headers.get('Content-Type') ?? '';
|
|
1981
|
+
if (!contentType.toLowerCase().includes('application/json')) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// Clone so we don't consume the body that will be sent
|
|
1986
|
+
let responseBody: unknown;
|
|
1987
|
+
try {
|
|
1988
|
+
const cloned = res.clone();
|
|
1989
|
+
responseBody = await cloned.json();
|
|
1990
|
+
} catch {
|
|
1991
|
+
const OutputValidationError = StructuredError('OutputValidationError')<{
|
|
1992
|
+
issues: any[];
|
|
1993
|
+
}>();
|
|
1994
|
+
throw new OutputValidationError({
|
|
1995
|
+
message: 'Output validation failed: response is not valid JSON',
|
|
1996
|
+
issues: [],
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
const result = await validateSchema(effectiveOutputSchema, responseBody);
|
|
2001
|
+
if (!result.success) {
|
|
2002
|
+
const OutputValidationError = StructuredError('OutputValidationError')<{
|
|
2003
|
+
issues: any[];
|
|
2004
|
+
}>();
|
|
2005
|
+
throw new OutputValidationError({
|
|
2006
|
+
message: `Output validation failed: ${formatValidationIssues(result.issues)}`,
|
|
2007
|
+
issues: result.issues,
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// Replace response with validated/sanitized JSON
|
|
2012
|
+
c.res = new Response(JSON.stringify(result.data), {
|
|
2013
|
+
status: res.status,
|
|
2014
|
+
headers: res.headers,
|
|
2015
|
+
});
|
|
2016
|
+
};
|
|
2017
|
+
|
|
2018
|
+
// If we have no input schema, we only do output validation
|
|
2019
|
+
if (!effectiveInputSchema) {
|
|
2020
|
+
return outputValidator as unknown as Handler;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// Compose: input validator → output validator
|
|
2024
|
+
const inputMiddleware = buildInputValidator(effectiveInputSchema);
|
|
2025
|
+
|
|
2026
|
+
const composed: MiddlewareHandler = async (c, next) => {
|
|
2027
|
+
// Run the validator first; its next() runs the output validator,
|
|
2028
|
+
// whose next() runs the actual handler(s)
|
|
2029
|
+
const result = await inputMiddleware(c, async () => {
|
|
2030
|
+
await outputValidator(c, next);
|
|
2031
|
+
});
|
|
2032
|
+
// If inputMiddleware returned early (validation failed), return that response
|
|
2033
|
+
return result;
|
|
2034
|
+
};
|
|
2035
|
+
|
|
2036
|
+
return composed as unknown as Handler;
|
|
2037
|
+
}) as AgentValidator<TInput, TOutput>;
|
|
2038
|
+
|
|
2039
|
+
// Register the agent for runtime use
|
|
2040
|
+
// @ts-expect-error - metadata might be incomplete until build plugin injects InternalAgentMetadata
|
|
2041
|
+
agents.set(name, agent as Agent<TInput, TOutput, TStream, TConfig, TAppState>);
|
|
2042
|
+
|
|
2043
|
+
// Create and return AgentRunner
|
|
2044
|
+
const runner: any = {
|
|
2045
|
+
metadata: metadata as AgentMetadata,
|
|
2046
|
+
validator: agent.validator,
|
|
2047
|
+
inputSchema: inputSchema as TInput,
|
|
2048
|
+
outputSchema: outputSchema as TOutput,
|
|
2049
|
+
stream: (config.schema?.stream as TStream) || (false as TStream),
|
|
2050
|
+
createEval,
|
|
2051
|
+
addEventListener: agent.addEventListener,
|
|
2052
|
+
removeEventListener: agent.removeEventListener,
|
|
2053
|
+
run: inputSchema
|
|
2054
|
+
? async (input: InferSchemaInput<Exclude<TInput, undefined>>) => {
|
|
2055
|
+
// Get tracer from AsyncLocalStorage context if available
|
|
2056
|
+
try {
|
|
2057
|
+
const agentCtx = getAgentContext();
|
|
2058
|
+
if (agentCtx?.tracer) {
|
|
2059
|
+
return runWithSpan<any, TInput, TOutput, TStream>(
|
|
2060
|
+
agentCtx.tracer,
|
|
2061
|
+
agent as Agent<TInput, TOutput, TStream>,
|
|
2062
|
+
async () => await agent.handler(input)
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
} catch {
|
|
2066
|
+
// Context not available, skip span creation
|
|
2067
|
+
}
|
|
2068
|
+
return await agent.handler(input);
|
|
2069
|
+
}
|
|
2070
|
+
: async () => {
|
|
2071
|
+
// Get tracer from AsyncLocalStorage context if available
|
|
2072
|
+
try {
|
|
2073
|
+
const agentCtx = getAgentContext();
|
|
2074
|
+
if (agentCtx?.tracer) {
|
|
2075
|
+
return runWithSpan<any, TInput, TOutput, TStream>(
|
|
2076
|
+
agentCtx.tracer,
|
|
2077
|
+
agent as Agent<TInput, TOutput, TStream>,
|
|
2078
|
+
async () => await agent.handler()
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
} catch {
|
|
2082
|
+
// Context not available, skip span creation
|
|
2083
|
+
}
|
|
2084
|
+
return await agent.handler();
|
|
2085
|
+
},
|
|
2086
|
+
[INTERNAL_AGENT]: agent, // Store reference to internal agent for testing
|
|
2087
|
+
};
|
|
2088
|
+
|
|
2089
|
+
return runner as AgentRunner<TInput, TOutput, TStream>;
|
|
1540
2090
|
}
|
|
1541
2091
|
|
|
1542
2092
|
const runWithSpan = async <
|
|
@@ -1581,7 +2131,7 @@ const createAgentRunner = <
|
|
|
1581
2131
|
return runWithSpan<any, TInput, TOutput, TStream>(
|
|
1582
2132
|
tracer,
|
|
1583
2133
|
agent,
|
|
1584
|
-
async () => await agent.handler(
|
|
2134
|
+
async () => await agent.handler(input)
|
|
1585
2135
|
);
|
|
1586
2136
|
},
|
|
1587
2137
|
} as AgentRunner<TInput, TOutput, TStream>;
|
|
@@ -1592,7 +2142,7 @@ const createAgentRunner = <
|
|
|
1592
2142
|
return runWithSpan<any, TInput, TOutput, TStream>(
|
|
1593
2143
|
tracer,
|
|
1594
2144
|
agent,
|
|
1595
|
-
async () => await agent.handler(
|
|
2145
|
+
async () => await agent.handler()
|
|
1596
2146
|
);
|
|
1597
2147
|
},
|
|
1598
2148
|
} as AgentRunner<TInput, TOutput, TStream>;
|
|
@@ -1601,41 +2151,36 @@ const createAgentRunner = <
|
|
|
1601
2151
|
|
|
1602
2152
|
/**
|
|
1603
2153
|
* Populate the agents object with all registered agents
|
|
2154
|
+
* Keys are converted to camelCase to match the generated TypeScript types
|
|
1604
2155
|
*/
|
|
1605
2156
|
export const populateAgentsRegistry = (ctx: Context): any => {
|
|
1606
2157
|
const agentsObj: any = {};
|
|
2158
|
+
// Track ownership of camelCase keys to detect collisions between different raw names
|
|
2159
|
+
const ownershipMap = new Map<string, string>();
|
|
1607
2160
|
|
|
1608
|
-
// Build
|
|
2161
|
+
// Build flat registry of agents
|
|
1609
2162
|
for (const [name, agentFn] of agents) {
|
|
1610
2163
|
const runner = createAgentRunner(agentFn, ctx);
|
|
2164
|
+
const key = toCamelCase(name);
|
|
1611
2165
|
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
if (parentAgent) {
|
|
1626
|
-
agentsObj[parentName] = createAgentRunner(parentAgent, ctx);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
// Attach subagent to parent using camelCase property name
|
|
1630
|
-
const camelChildName = toCamelCase(childName);
|
|
1631
|
-
if (agentsObj[parentName]) {
|
|
1632
|
-
agentsObj[parentName][camelChildName] = runner;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
} else {
|
|
1636
|
-
// Parent agent or standalone agent
|
|
1637
|
-
agentsObj[name] = runner;
|
|
2166
|
+
// Validate key is non-empty
|
|
2167
|
+
if (!key) {
|
|
2168
|
+
internal.warn(`Agent name "${name}" converts to empty camelCase key. Skipping.`);
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Detect collision on key - check ownership
|
|
2173
|
+
const existingOwner = ownershipMap.get(key);
|
|
2174
|
+
if (existingOwner && existingOwner !== name) {
|
|
2175
|
+
internal.error(
|
|
2176
|
+
`Agent registry collision: "${name}" conflicts with "${existingOwner}" (both map to camelCase key "${key}")`
|
|
2177
|
+
);
|
|
2178
|
+
throw new Error(`Agent registry collision detected for key "${key}"`);
|
|
1638
2179
|
}
|
|
2180
|
+
|
|
2181
|
+
agentsObj[key] = runner;
|
|
2182
|
+
// Record ownership
|
|
2183
|
+
ownershipMap.set(key, name);
|
|
1639
2184
|
}
|
|
1640
2185
|
|
|
1641
2186
|
return agentsObj;
|
|
@@ -1646,32 +2191,16 @@ export const createAgentMiddleware = (agentName: AgentName | ''): MiddlewareHand
|
|
|
1646
2191
|
// Populate agents object with strongly-typed keys
|
|
1647
2192
|
const agentsObj = populateAgentsRegistry(ctx);
|
|
1648
2193
|
|
|
1649
|
-
//
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
const parts = agentName.split('.');
|
|
1659
|
-
const parentName = parts[0];
|
|
1660
|
-
const childName = parts[1];
|
|
1661
|
-
if (parentName && childName) {
|
|
1662
|
-
currentAgent = agentsObj[parentName]?.[childName];
|
|
1663
|
-
parentAgent = agentsObj[parentName];
|
|
2194
|
+
// Track agent ID for session telemetry
|
|
2195
|
+
if (agentName) {
|
|
2196
|
+
const agentKey = toCamelCase(agentName);
|
|
2197
|
+
const agent = agentsObj[agentKey];
|
|
2198
|
+
const _ctx = privateContext(ctx);
|
|
2199
|
+
if (agent?.metadata?.id) {
|
|
2200
|
+
// we add both so that you can query by either
|
|
2201
|
+
_ctx.var.agentIds.add(agent.metadata.id);
|
|
2202
|
+
_ctx.var.agentIds.add(agent.metadata.agentId);
|
|
1664
2203
|
}
|
|
1665
|
-
} else if (agentName) {
|
|
1666
|
-
// This is a parent or standalone agent
|
|
1667
|
-
currentAgent = agentsObj[agentName];
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
const _ctx = privateContext(ctx);
|
|
1671
|
-
if (currentAgent?.metadata?.id) {
|
|
1672
|
-
// we add both so that you can query by either
|
|
1673
|
-
_ctx.var.agentIds.add(currentAgent.metadata.id);
|
|
1674
|
-
_ctx.var.agentIds.add(currentAgent.metadata.agentId);
|
|
1675
2204
|
}
|
|
1676
2205
|
|
|
1677
2206
|
const sessionId = ctx.var.sessionId;
|
|
@@ -1680,18 +2209,9 @@ export const createAgentMiddleware = (agentName: AgentName | ''): MiddlewareHand
|
|
|
1680
2209
|
const config = agentName ? getAgentConfig(agentName as AgentName) : undefined;
|
|
1681
2210
|
const app = ctx.var.app;
|
|
1682
2211
|
|
|
1683
|
-
const args: RequestAgentContextArgs<
|
|
1684
|
-
AgentRegistry,
|
|
1685
|
-
AgentRunner | undefined,
|
|
1686
|
-
AgentRunner | undefined,
|
|
1687
|
-
unknown,
|
|
1688
|
-
unknown
|
|
1689
|
-
> = {
|
|
2212
|
+
const args: RequestAgentContextArgs<AgentRegistry, unknown, unknown> = {
|
|
1690
2213
|
agent: agentsObj,
|
|
1691
|
-
|
|
1692
|
-
parent: parentAgent,
|
|
1693
|
-
agentName: agentName as AgentName,
|
|
1694
|
-
logger: ctx.var.logger.child({ agent: agentName }),
|
|
2214
|
+
logger: ctx.var.logger,
|
|
1695
2215
|
tracer: ctx.var.tracer,
|
|
1696
2216
|
sessionId,
|
|
1697
2217
|
session,
|
|
@@ -1699,9 +2219,10 @@ export const createAgentMiddleware = (agentName: AgentName | ''): MiddlewareHand
|
|
|
1699
2219
|
handler: ctx.var.waitUntilHandler,
|
|
1700
2220
|
config: config || {},
|
|
1701
2221
|
app: app || {},
|
|
2222
|
+
runtime: getGlobalRuntimeState(),
|
|
1702
2223
|
};
|
|
1703
2224
|
|
|
1704
|
-
return
|
|
2225
|
+
return setupRequestAgentContext(ctx as unknown as Record<string, unknown>, args, next);
|
|
1705
2226
|
};
|
|
1706
2227
|
};
|
|
1707
2228
|
|
|
@@ -1718,10 +2239,89 @@ export const runAgentSetups = async (appState: AppState): Promise<void> => {
|
|
|
1718
2239
|
};
|
|
1719
2240
|
|
|
1720
2241
|
export const runAgentShutdowns = async (appState: AppState): Promise<void> => {
|
|
1721
|
-
|
|
2242
|
+
const runtime = getGlobalRuntimeState();
|
|
2243
|
+
for (const [name, agent] of runtime.agents.entries()) {
|
|
1722
2244
|
if (agent.shutdown) {
|
|
1723
|
-
const config =
|
|
2245
|
+
const config = runtime.agentConfigs.get(name) as any;
|
|
1724
2246
|
await agent.shutdown(appState, config);
|
|
1725
2247
|
}
|
|
1726
2248
|
}
|
|
1727
2249
|
};
|
|
2250
|
+
|
|
2251
|
+
/**
|
|
2252
|
+
* Run an agent within a specific AgentContext.
|
|
2253
|
+
* Sets up AsyncLocalStorage with the provided context and executes the agent.
|
|
2254
|
+
*
|
|
2255
|
+
* This is the recommended way to test agents in unit tests. It automatically:
|
|
2256
|
+
* - Registers the agent in the runtime state so event listeners fire
|
|
2257
|
+
* - Sets up AsyncLocalStorage so getAgentContext() works inside the agent
|
|
2258
|
+
* - Handles both agents with input and agents without input
|
|
2259
|
+
*
|
|
2260
|
+
* **Use cases:**
|
|
2261
|
+
* - Unit testing agents with TestAgentContext
|
|
2262
|
+
* - Running agents outside HTTP request flow
|
|
2263
|
+
* - Custom agent execution environments
|
|
2264
|
+
* - Testing event listeners and evaluations
|
|
2265
|
+
*
|
|
2266
|
+
* @template TInput - Type of the input parameter
|
|
2267
|
+
* @template TOutput - Type of the return value
|
|
2268
|
+
*
|
|
2269
|
+
* @param ctx - The AgentContext to use (typically TestAgentContext in tests)
|
|
2270
|
+
* @param agent - The AgentRunner to execute (returned from createAgent)
|
|
2271
|
+
* @param input - Input data (required if agent has input schema, omit otherwise)
|
|
2272
|
+
*
|
|
2273
|
+
* @returns Promise resolving to the agent's output
|
|
2274
|
+
*
|
|
2275
|
+
* @example
|
|
2276
|
+
* ```typescript
|
|
2277
|
+
* import { runInAgentContext, TestAgentContext } from '@agentuity/runtime/test';
|
|
2278
|
+
*
|
|
2279
|
+
* test('greeting agent', async () => {
|
|
2280
|
+
* const ctx = new TestAgentContext();
|
|
2281
|
+
* const result = await runInAgentContext(ctx, greetingAgent, {
|
|
2282
|
+
* name: 'Alice',
|
|
2283
|
+
* age: 30
|
|
2284
|
+
* });
|
|
2285
|
+
* expect(result).toBe('Hello, Alice! You are 30 years old.');
|
|
2286
|
+
* });
|
|
2287
|
+
*
|
|
2288
|
+
* test('no-input agent', async () => {
|
|
2289
|
+
* const ctx = new TestAgentContext();
|
|
2290
|
+
* const result = await runInAgentContext(ctx, statusAgent);
|
|
2291
|
+
* expect(result).toEqual({ status: 'ok' });
|
|
2292
|
+
* });
|
|
2293
|
+
* ```
|
|
2294
|
+
*/
|
|
2295
|
+
export async function runInAgentContext<TInput, TOutput>(
|
|
2296
|
+
ctx: AgentContext<any, any, any>,
|
|
2297
|
+
agent: AgentRunner<any, any, any>,
|
|
2298
|
+
input?: TInput
|
|
2299
|
+
): Promise<TOutput> {
|
|
2300
|
+
const { getAgentAsyncLocalStorage } = await import('./_context');
|
|
2301
|
+
const storage = getAgentAsyncLocalStorage();
|
|
2302
|
+
|
|
2303
|
+
// Register agent in runtime state so events fire (lookup by metadata.name)
|
|
2304
|
+
const agentName = agent.metadata.name;
|
|
2305
|
+
const runtime = getAgentRuntime(ctx);
|
|
2306
|
+
|
|
2307
|
+
// Get internal agent from runner (stored via symbol) or global registry
|
|
2308
|
+
const internalAgent = (agent as any)[INTERNAL_AGENT] || agents.get(agentName);
|
|
2309
|
+
|
|
2310
|
+
if (internalAgent && agentName) {
|
|
2311
|
+
runtime.agents.set(agentName, internalAgent);
|
|
2312
|
+
|
|
2313
|
+
// Copy event listeners from global to context runtime
|
|
2314
|
+
const globalListeners = agentEventListeners.get(internalAgent);
|
|
2315
|
+
if (globalListeners) {
|
|
2316
|
+
runtime.agentEventListeners.set(internalAgent, globalListeners);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
return storage.run(ctx, async () => {
|
|
2321
|
+
if (input !== undefined) {
|
|
2322
|
+
return await (agent.run as any)(input);
|
|
2323
|
+
} else {
|
|
2324
|
+
return await (agent.run as any)();
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
}
|