@agentuity/runtime 0.0.68 → 0.0.69
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 +45 -3
- package/README.md +6 -6
- package/dist/_server.js +4 -0
- package/dist/_server.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 +190 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +158 -19
- package/dist/agent.js.map +1 -1
- package/dist/agent.validator.test.d.ts +2 -0
- package/dist/agent.validator.test.d.ts.map +1 -0
- package/dist/agent.validator.test.js +508 -0
- package/dist/agent.validator.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/session.d.ts +16 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +6 -0
- package/dist/session.js.map +1 -1
- package/package.json +3 -3
- package/src/_server.ts +4 -0
- package/src/_validation.ts +123 -0
- package/src/agent.ts +412 -19
- package/src/agent.validator.test.ts +587 -0
- package/src/index.ts +1 -0
- package/src/session.ts +20 -0
package/src/agent.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
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 type { Handler } from 'hono/types';
|
|
14
|
+
import { validator } from 'hono/validator';
|
|
13
15
|
import { getAgentContext, runInAgentContext, type RequestAgentContextArgs } from './_context';
|
|
14
16
|
import type { Logger } from './logger';
|
|
15
17
|
import type {
|
|
@@ -29,6 +31,7 @@ import { getEvalRunEventProvider } from './_services';
|
|
|
29
31
|
import * as runtimeConfig from './_config';
|
|
30
32
|
import type { EvalRunStartEvent } from '@agentuity/core';
|
|
31
33
|
import type { AppState } from './index';
|
|
34
|
+
import { validateSchema, formatValidationIssues } from './_validation';
|
|
32
35
|
|
|
33
36
|
export type AgentEventName = 'started' | 'completed' | 'errored';
|
|
34
37
|
|
|
@@ -394,6 +397,147 @@ type CreateEvalMethod<
|
|
|
394
397
|
TOutput extends StandardSchemaV1 | undefined = any,
|
|
395
398
|
> = (config: CreateEvalConfig<TInput, TOutput>) => Eval<TInput, TOutput>;
|
|
396
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Validator function type with method overloads for different validation scenarios.
|
|
402
|
+
* Provides type-safe validation middleware that integrates with Hono's type system.
|
|
403
|
+
*
|
|
404
|
+
* This validator automatically validates incoming JSON request bodies using StandardSchema-compatible
|
|
405
|
+
* schemas (Zod, Valibot, ArkType, etc.) and provides full TypeScript type inference for validated data
|
|
406
|
+
* accessible via `c.req.valid('json')`.
|
|
407
|
+
*
|
|
408
|
+
* The validator returns 400 Bad Request with validation error details if validation fails.
|
|
409
|
+
*
|
|
410
|
+
* @template TInput - Agent's input schema type (StandardSchemaV1 or undefined)
|
|
411
|
+
* @template _TOutput - Agent's output schema type (reserved for future output validation)
|
|
412
|
+
*
|
|
413
|
+
* @example Basic usage with agent's schema
|
|
414
|
+
* ```typescript
|
|
415
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
416
|
+
* const data = c.req.valid('json'); // Fully typed from agent's input schema
|
|
417
|
+
* return c.json(data);
|
|
418
|
+
* });
|
|
419
|
+
* ```
|
|
420
|
+
*
|
|
421
|
+
* @example Override with custom input schema
|
|
422
|
+
* ```typescript
|
|
423
|
+
* router.post('/custom', agent.validator({ input: z.object({ id: z.string() }) }), async (c) => {
|
|
424
|
+
* const data = c.req.valid('json'); // Typed as { id: string }
|
|
425
|
+
* return c.json(data);
|
|
426
|
+
* });
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
export interface AgentValidator<
|
|
430
|
+
TInput extends StandardSchemaV1 | undefined,
|
|
431
|
+
_TOutput extends StandardSchemaV1 | undefined,
|
|
432
|
+
> {
|
|
433
|
+
/**
|
|
434
|
+
* Validates using the agent's input schema (no override).
|
|
435
|
+
* Returns Hono middleware handler that validates JSON request body.
|
|
436
|
+
*
|
|
437
|
+
* @returns Middleware handler with type inference for validated data
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
* ```typescript
|
|
441
|
+
* // Agent has schema: { input: z.object({ name: z.string() }) }
|
|
442
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
443
|
+
* const data = c.req.valid('json'); // { name: string }
|
|
444
|
+
* return c.json({ received: data.name });
|
|
445
|
+
* });
|
|
446
|
+
* ```
|
|
447
|
+
*/
|
|
448
|
+
(): TInput extends StandardSchemaV1
|
|
449
|
+
? Handler<
|
|
450
|
+
any,
|
|
451
|
+
any,
|
|
452
|
+
{
|
|
453
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
454
|
+
in: {};
|
|
455
|
+
out: { json: StandardSchemaV1.InferOutput<TInput> };
|
|
456
|
+
}
|
|
457
|
+
>
|
|
458
|
+
: Handler<any, any, any>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Output-only validation override.
|
|
462
|
+
* Validates only the response body (no input validation).
|
|
463
|
+
*
|
|
464
|
+
* Useful for GET routes or routes where input validation is handled elsewhere.
|
|
465
|
+
* The middleware validates the JSON response body and throws 500 Internal Server Error
|
|
466
|
+
* if validation fails.
|
|
467
|
+
*
|
|
468
|
+
* @template TOverrideOutput - Custom output schema type
|
|
469
|
+
* @param override - Object containing output schema
|
|
470
|
+
* @returns Middleware handler that validates response output
|
|
471
|
+
*
|
|
472
|
+
* @example GET route with output validation
|
|
473
|
+
* ```typescript
|
|
474
|
+
* router.get('/', agent.validator({ output: z.array(z.object({ id: z.string() })) }), async (c) => {
|
|
475
|
+
* // Returns array of objects - validated against schema
|
|
476
|
+
* return c.json([{ id: '123' }, { id: '456' }]);
|
|
477
|
+
* });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
<TOverrideOutput extends StandardSchemaV1>(override: {
|
|
481
|
+
output: TOverrideOutput;
|
|
482
|
+
}): Handler<
|
|
483
|
+
any,
|
|
484
|
+
any,
|
|
485
|
+
{
|
|
486
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
487
|
+
in: {};
|
|
488
|
+
out: { json: StandardSchemaV1.InferOutput<TOverrideOutput> };
|
|
489
|
+
}
|
|
490
|
+
>;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Validates with custom input and optional output schemas (POST/PUT/PATCH/DELETE).
|
|
494
|
+
* Overrides the agent's schema for this specific route.
|
|
495
|
+
*
|
|
496
|
+
* @template TOverrideInput - Custom input schema type
|
|
497
|
+
* @template TOverrideOutput - Optional custom output schema type
|
|
498
|
+
* @param override - Object containing input (required) and output (optional) schemas
|
|
499
|
+
* @returns Middleware handler with type inference from custom schemas
|
|
500
|
+
*
|
|
501
|
+
* @example Custom input schema
|
|
502
|
+
* ```typescript
|
|
503
|
+
* router.post('/users', agent.validator({
|
|
504
|
+
* input: z.object({ email: z.string().email(), name: z.string() })
|
|
505
|
+
* }), async (c) => {
|
|
506
|
+
* const data = c.req.valid('json'); // { email: string, name: string }
|
|
507
|
+
* return c.json({ id: '123', ...data });
|
|
508
|
+
* });
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* @example Custom input and output schemas
|
|
512
|
+
* ```typescript
|
|
513
|
+
* router.post('/convert', agent.validator({
|
|
514
|
+
* input: z.string(),
|
|
515
|
+
* output: z.number()
|
|
516
|
+
* }), async (c) => {
|
|
517
|
+
* const data = c.req.valid('json'); // string
|
|
518
|
+
* return c.json(123);
|
|
519
|
+
* });
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
522
|
+
<
|
|
523
|
+
TOverrideInput extends StandardSchemaV1,
|
|
524
|
+
TOverrideOutput extends StandardSchemaV1 | undefined = undefined,
|
|
525
|
+
>(override: {
|
|
526
|
+
input: TOverrideInput;
|
|
527
|
+
output?: TOverrideOutput;
|
|
528
|
+
}): Handler<
|
|
529
|
+
any,
|
|
530
|
+
any,
|
|
531
|
+
{
|
|
532
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
533
|
+
in: {};
|
|
534
|
+
out: {
|
|
535
|
+
json: StandardSchemaV1.InferOutput<TOverrideInput>;
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
>;
|
|
539
|
+
}
|
|
540
|
+
|
|
397
541
|
/**
|
|
398
542
|
* Agent instance type returned by createAgent().
|
|
399
543
|
* Represents a fully configured agent with metadata, handler, lifecycle hooks, and event listeners.
|
|
@@ -449,6 +593,76 @@ export type Agent<
|
|
|
449
593
|
...args: any[]
|
|
450
594
|
) => any | Promise<any>;
|
|
451
595
|
|
|
596
|
+
/**
|
|
597
|
+
* Creates a type-safe validation middleware for routes using StandardSchema validation.
|
|
598
|
+
*
|
|
599
|
+
* This method validates incoming JSON request bodies against the agent's **input schema**
|
|
600
|
+
* and optionally validates outgoing JSON responses against the **output schema**.
|
|
601
|
+
* Provides full TypeScript type inference for validated input data accessible via `c.req.valid('json')`.
|
|
602
|
+
*
|
|
603
|
+
* **Validation behavior:**
|
|
604
|
+
* - **Input**: Validates request JSON body, returns 400 Bad Request on failure
|
|
605
|
+
* - **Output**: Validates response JSON body (if output schema provided), throws 500 on failure
|
|
606
|
+
* - Passes validated input data to handler via `c.req.valid('json')`
|
|
607
|
+
* - Full TypeScript type inference for validated input data
|
|
608
|
+
*
|
|
609
|
+
* **Supported schema libraries:**
|
|
610
|
+
* - Zod (z.object, z.string, etc.)
|
|
611
|
+
* - Valibot (v.object, v.string, etc.)
|
|
612
|
+
* - ArkType (type({ ... }))
|
|
613
|
+
* - Any StandardSchema-compatible library
|
|
614
|
+
*
|
|
615
|
+
* **Method overloads:**
|
|
616
|
+
* - `agent.validator()` - Validates using agent's input/output schemas
|
|
617
|
+
* - `agent.validator({ output: schema })` - Output-only validation (no input validation)
|
|
618
|
+
* - `agent.validator({ input: schema })` - Custom input schema override
|
|
619
|
+
* - `agent.validator({ input: schema, output: schema })` - Both input and output validated
|
|
620
|
+
*
|
|
621
|
+
* @returns Hono middleware handler with proper type inference
|
|
622
|
+
*
|
|
623
|
+
* @example Automatic validation using agent's schema
|
|
624
|
+
* ```typescript
|
|
625
|
+
* // Agent defined with: schema: { input: z.object({ name: z.string(), age: z.number() }) }
|
|
626
|
+
* router.post('/', agent.validator(), async (c) => {
|
|
627
|
+
* const data = c.req.valid('json'); // Fully typed: { name: string, age: number }
|
|
628
|
+
* return c.json({ greeting: `Hello ${data.name}, age ${data.age}` });
|
|
629
|
+
* });
|
|
630
|
+
* ```
|
|
631
|
+
*
|
|
632
|
+
* @example Override with custom schema per-route
|
|
633
|
+
* ```typescript
|
|
634
|
+
* router.post('/email', agent.validator({
|
|
635
|
+
* input: z.object({ email: z.string().email() })
|
|
636
|
+
* }), async (c) => {
|
|
637
|
+
* const data = c.req.valid('json'); // Typed as { email: string }
|
|
638
|
+
* return c.json({ sent: data.email });
|
|
639
|
+
* });
|
|
640
|
+
* ```
|
|
641
|
+
*
|
|
642
|
+
* @example Works with any StandardSchema library
|
|
643
|
+
* ```typescript
|
|
644
|
+
* import * as v from 'valibot';
|
|
645
|
+
*
|
|
646
|
+
* router.post('/valibot', agent.validator({
|
|
647
|
+
* input: v.object({ count: v.number() })
|
|
648
|
+
* }), async (c) => {
|
|
649
|
+
* const data = c.req.valid('json'); // Typed correctly
|
|
650
|
+
* return c.json({ count: data.count });
|
|
651
|
+
* });
|
|
652
|
+
* ```
|
|
653
|
+
*
|
|
654
|
+
* @example Validation error response (400)
|
|
655
|
+
* ```typescript
|
|
656
|
+
* // Request: { "name": "Bob" } (missing 'age')
|
|
657
|
+
* // Response: {
|
|
658
|
+
* // "error": "Validation failed",
|
|
659
|
+
* // "message": "age: Invalid input: expected number, received undefined",
|
|
660
|
+
* // "issues": [{ "message": "...", "path": ["age"] }]
|
|
661
|
+
* // }
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
validator: AgentValidator<TInput, TOutput>;
|
|
665
|
+
|
|
452
666
|
/**
|
|
453
667
|
* Array of evaluation functions created via agent.createEval().
|
|
454
668
|
* Used for testing and validating agent behavior.
|
|
@@ -1536,6 +1750,108 @@ export function createAgent<
|
|
|
1536
1750
|
agent.stream = config.schema.stream;
|
|
1537
1751
|
}
|
|
1538
1752
|
|
|
1753
|
+
// Add validator method with overloads
|
|
1754
|
+
agent.validator = ((override?: any) => {
|
|
1755
|
+
const effectiveInputSchema = override?.input ?? inputSchema;
|
|
1756
|
+
const effectiveOutputSchema = override?.output ?? outputSchema;
|
|
1757
|
+
|
|
1758
|
+
// Helper to build the standard Hono input validator so types flow
|
|
1759
|
+
const buildInputValidator = (schema?: StandardSchemaV1) =>
|
|
1760
|
+
validator('json', async (value, c) => {
|
|
1761
|
+
if (schema) {
|
|
1762
|
+
const result = await validateSchema(schema, value);
|
|
1763
|
+
if (!result.success) {
|
|
1764
|
+
return c.json(
|
|
1765
|
+
{
|
|
1766
|
+
error: 'Validation failed',
|
|
1767
|
+
message: formatValidationIssues(result.issues),
|
|
1768
|
+
issues: result.issues,
|
|
1769
|
+
},
|
|
1770
|
+
400
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
return result.data;
|
|
1774
|
+
}
|
|
1775
|
+
return value;
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
// If no output schema, preserve existing behavior: pure input validation
|
|
1779
|
+
if (!effectiveOutputSchema) {
|
|
1780
|
+
return buildInputValidator(effectiveInputSchema);
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// Output validation middleware (runs after handler)
|
|
1784
|
+
const outputValidator: MiddlewareHandler = async (c, next) => {
|
|
1785
|
+
await next();
|
|
1786
|
+
|
|
1787
|
+
const res = c.res;
|
|
1788
|
+
if (!res) return;
|
|
1789
|
+
|
|
1790
|
+
// Skip output validation for streaming agents
|
|
1791
|
+
if (config.schema?.stream) {
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// Only validate JSON responses
|
|
1796
|
+
const contentType = res.headers.get('Content-Type') ?? '';
|
|
1797
|
+
if (!contentType.toLowerCase().includes('application/json')) {
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Clone so we don't consume the body that will be sent
|
|
1802
|
+
let responseBody: unknown;
|
|
1803
|
+
try {
|
|
1804
|
+
const cloned = res.clone();
|
|
1805
|
+
responseBody = await cloned.json();
|
|
1806
|
+
} catch {
|
|
1807
|
+
const OutputValidationError = StructuredError('OutputValidationError')<{
|
|
1808
|
+
issues: any[];
|
|
1809
|
+
}>();
|
|
1810
|
+
throw new OutputValidationError({
|
|
1811
|
+
message: 'Output validation failed: response is not valid JSON',
|
|
1812
|
+
issues: [],
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
const result = await validateSchema(effectiveOutputSchema, responseBody);
|
|
1817
|
+
if (!result.success) {
|
|
1818
|
+
const OutputValidationError = StructuredError('OutputValidationError')<{
|
|
1819
|
+
issues: any[];
|
|
1820
|
+
}>();
|
|
1821
|
+
throw new OutputValidationError({
|
|
1822
|
+
message: `Output validation failed: ${formatValidationIssues(result.issues)}`,
|
|
1823
|
+
issues: result.issues,
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Replace response with validated/sanitized JSON
|
|
1828
|
+
c.res = new Response(JSON.stringify(result.data), {
|
|
1829
|
+
status: res.status,
|
|
1830
|
+
headers: res.headers,
|
|
1831
|
+
});
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
// If we have no input schema, we only do output validation
|
|
1835
|
+
if (!effectiveInputSchema) {
|
|
1836
|
+
return outputValidator as unknown as Handler;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// Compose: input validator → output validator
|
|
1840
|
+
const inputMiddleware = buildInputValidator(effectiveInputSchema);
|
|
1841
|
+
|
|
1842
|
+
const composed: MiddlewareHandler = async (c, next) => {
|
|
1843
|
+
// Run the validator first; its next() runs the output validator,
|
|
1844
|
+
// whose next() runs the actual handler(s)
|
|
1845
|
+
const result = await inputMiddleware(c, async () => {
|
|
1846
|
+
await outputValidator(c, next);
|
|
1847
|
+
});
|
|
1848
|
+
// If inputMiddleware returned early (validation failed), return that response
|
|
1849
|
+
return result;
|
|
1850
|
+
};
|
|
1851
|
+
|
|
1852
|
+
return composed as unknown as Handler;
|
|
1853
|
+
}) as AgentValidator<TInput, TOutput>;
|
|
1854
|
+
|
|
1539
1855
|
return agent as Agent<TInput, TOutput, TStream, TConfig, TAppState>;
|
|
1540
1856
|
}
|
|
1541
1857
|
|
|
@@ -1601,9 +1917,13 @@ const createAgentRunner = <
|
|
|
1601
1917
|
|
|
1602
1918
|
/**
|
|
1603
1919
|
* Populate the agents object with all registered agents
|
|
1920
|
+
* Keys are converted to camelCase to match the generated TypeScript types
|
|
1604
1921
|
*/
|
|
1605
1922
|
export const populateAgentsRegistry = (ctx: Context): any => {
|
|
1606
1923
|
const agentsObj: any = {};
|
|
1924
|
+
// Track ownership of camelCase keys to detect collisions between different raw names
|
|
1925
|
+
const ownershipMap = new Map<string, string>();
|
|
1926
|
+
const childOwnershipMap = new Map<string, string>();
|
|
1607
1927
|
|
|
1608
1928
|
// Build nested structure for agents and subagents
|
|
1609
1929
|
for (const [name, agentFn] of agents) {
|
|
@@ -1616,25 +1936,94 @@ export const populateAgentsRegistry = (ctx: Context): any => {
|
|
|
1616
1936
|
internal.warn(`Invalid subagent name format: "${name}". Expected "parent.child".`);
|
|
1617
1937
|
continue;
|
|
1618
1938
|
}
|
|
1619
|
-
const
|
|
1620
|
-
const
|
|
1621
|
-
if (
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1939
|
+
const rawParentName = parts[0];
|
|
1940
|
+
const rawChildName = parts[1];
|
|
1941
|
+
if (rawParentName && rawChildName) {
|
|
1942
|
+
// Convert parent name to camelCase for registry key
|
|
1943
|
+
const parentKey = toCamelCase(rawParentName);
|
|
1944
|
+
|
|
1945
|
+
// Validate parentKey is non-empty
|
|
1946
|
+
if (!parentKey) {
|
|
1947
|
+
internal.warn(
|
|
1948
|
+
`Agent name "${rawParentName}" converts to empty camelCase key. Skipping.`
|
|
1949
|
+
);
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// Detect collision on parent key - check ownership
|
|
1954
|
+
const existingOwner = ownershipMap.get(parentKey);
|
|
1955
|
+
if (existingOwner && existingOwner !== rawParentName) {
|
|
1956
|
+
internal.error(
|
|
1957
|
+
`Agent registry collision: "${rawParentName}" conflicts with "${existingOwner}" (both map to camelCase key "${parentKey}")`
|
|
1958
|
+
);
|
|
1959
|
+
throw new Error(`Agent registry collision detected for key "${parentKey}"`);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
if (!agentsObj[parentKey]) {
|
|
1963
|
+
// Ensure parent exists - look up by raw name in agents map
|
|
1964
|
+
const parentAgent = agents.get(rawParentName);
|
|
1625
1965
|
if (parentAgent) {
|
|
1626
|
-
agentsObj[
|
|
1966
|
+
agentsObj[parentKey] = createAgentRunner(parentAgent, ctx);
|
|
1967
|
+
// Record ownership
|
|
1968
|
+
ownershipMap.set(parentKey, rawParentName);
|
|
1627
1969
|
}
|
|
1628
1970
|
}
|
|
1971
|
+
|
|
1629
1972
|
// Attach subagent to parent using camelCase property name
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1632
|
-
|
|
1973
|
+
const childKey = toCamelCase(rawChildName);
|
|
1974
|
+
|
|
1975
|
+
// Validate childKey is non-empty
|
|
1976
|
+
if (!childKey) {
|
|
1977
|
+
internal.warn(
|
|
1978
|
+
`Agent name "${rawChildName}" converts to empty camelCase key. Skipping subagent "${name}".`
|
|
1979
|
+
);
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Detect collision on child key - check ownership
|
|
1984
|
+
const childOwnershipKey = `${parentKey}.${childKey}`;
|
|
1985
|
+
const existingChildOwner = childOwnershipMap.get(childOwnershipKey);
|
|
1986
|
+
if (existingChildOwner && existingChildOwner !== name) {
|
|
1987
|
+
internal.error(
|
|
1988
|
+
`Agent registry collision: subagent "${name}" conflicts with "${existingChildOwner}" (both map to camelCase key "${childOwnershipKey}")`
|
|
1989
|
+
);
|
|
1990
|
+
throw new Error(
|
|
1991
|
+
`Agent registry collision detected for subagent key "${childOwnershipKey}"`
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
if (agentsObj[parentKey]) {
|
|
1996
|
+
if (agentsObj[parentKey][childKey] === undefined) {
|
|
1997
|
+
agentsObj[parentKey][childKey] = runner;
|
|
1998
|
+
// Record ownership
|
|
1999
|
+
childOwnershipMap.set(childOwnershipKey, name);
|
|
2000
|
+
}
|
|
1633
2001
|
}
|
|
1634
2002
|
}
|
|
1635
2003
|
} else {
|
|
1636
|
-
// Parent agent or standalone agent
|
|
1637
|
-
|
|
2004
|
+
// Parent agent or standalone agent - convert to camelCase for registry key
|
|
2005
|
+
const parentKey = toCamelCase(name);
|
|
2006
|
+
|
|
2007
|
+
// Validate parentKey is non-empty
|
|
2008
|
+
if (!parentKey) {
|
|
2009
|
+
internal.warn(`Agent name "${name}" converts to empty camelCase key. Skipping.`);
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Detect collision on parent key - check ownership
|
|
2014
|
+
const existingOwner = ownershipMap.get(parentKey);
|
|
2015
|
+
if (existingOwner && existingOwner !== name) {
|
|
2016
|
+
internal.error(
|
|
2017
|
+
`Agent registry collision: "${name}" conflicts with "${existingOwner}" (both map to camelCase key "${parentKey}")`
|
|
2018
|
+
);
|
|
2019
|
+
throw new Error(`Agent registry collision detected for key "${parentKey}"`);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (!agentsObj[parentKey]) {
|
|
2023
|
+
agentsObj[parentKey] = runner;
|
|
2024
|
+
// Record ownership
|
|
2025
|
+
ownershipMap.set(parentKey, name);
|
|
2026
|
+
}
|
|
1638
2027
|
}
|
|
1639
2028
|
}
|
|
1640
2029
|
|
|
@@ -1656,15 +2045,19 @@ export const createAgentMiddleware = (agentName: AgentName | ''): MiddlewareHand
|
|
|
1656
2045
|
if (agentName?.includes('.')) {
|
|
1657
2046
|
// This is a subagent
|
|
1658
2047
|
const parts = agentName.split('.');
|
|
1659
|
-
const
|
|
1660
|
-
const
|
|
1661
|
-
if (
|
|
1662
|
-
|
|
1663
|
-
|
|
2048
|
+
const rawParentName = parts[0];
|
|
2049
|
+
const rawChildName = parts[1];
|
|
2050
|
+
if (rawParentName && rawChildName) {
|
|
2051
|
+
// Use camelCase keys to look up in agentsObj (which uses camelCase keys)
|
|
2052
|
+
const parentKey = toCamelCase(rawParentName);
|
|
2053
|
+
const childKey = toCamelCase(rawChildName);
|
|
2054
|
+
currentAgent = agentsObj[parentKey]?.[childKey];
|
|
2055
|
+
parentAgent = agentsObj[parentKey];
|
|
1664
2056
|
}
|
|
1665
2057
|
} else if (agentName) {
|
|
1666
|
-
// This is a parent or standalone agent
|
|
1667
|
-
|
|
2058
|
+
// This is a parent or standalone agent - use camelCase key
|
|
2059
|
+
const parentKey = toCamelCase(agentName);
|
|
2060
|
+
currentAgent = agentsObj[parentKey];
|
|
1668
2061
|
}
|
|
1669
2062
|
|
|
1670
2063
|
const _ctx = privateContext(ctx);
|