@actuarial-ts/agents 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -128
- package/dist/diagnostics.d.ts +69 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +655 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/divergence.d.ts +4 -3
- package/dist/divergence.d.ts.map +1 -1
- package/dist/divergence.js +30 -10
- package/dist/divergence.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/judgment.d.ts +75 -2
- package/dist/judgment.d.ts.map +1 -1
- package/dist/judgment.js +28 -10
- package/dist/judgment.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/promotion.d.ts.map +1 -1
- package/dist/promotion.js +2 -0
- package/dist/promotion.js.map +1 -1
- package/dist/remote.d.ts +7 -9
- package/dist/remote.d.ts.map +1 -1
- package/dist/tools.d.ts +26 -12
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +132 -23
- package/dist/tools.js.map +1 -1
- package/package.json +12 -11
- package/src/diagnostics.ts +833 -0
- package/src/divergence.ts +73 -29
- package/src/errors.ts +7 -0
- package/src/index.ts +1 -0
- package/src/judgment.ts +56 -20
- package/src/mcp.ts +2 -2
- package/src/promotion.ts +2 -0
- package/src/tools.ts +351 -54
package/src/tools.ts
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
* keep their code; everything else gets the fallback.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { createTool } from "@mastra/core/tools";
|
|
20
|
+
import { createTool, type Tool } from "@mastra/core/tools";
|
|
21
|
+
import {
|
|
22
|
+
toStandardSchema,
|
|
23
|
+
type StandardSchemaWithJSON,
|
|
24
|
+
} from "@mastra/core/schema";
|
|
21
25
|
import type { z } from "zod";
|
|
22
26
|
import { AgentsError } from "./errors.js";
|
|
23
27
|
|
|
@@ -26,17 +30,51 @@ import { AgentsError } from "./errors.js";
|
|
|
26
30
|
|
|
27
31
|
/** The uniform tool-failure shape: agents branch on success, hosts log code. */
|
|
28
32
|
export type ToolEnvelopeFailure = {
|
|
29
|
-
success: false;
|
|
30
|
-
error: { code: string; message: string };
|
|
33
|
+
readonly success: false;
|
|
34
|
+
readonly error: { readonly code: string; readonly message: string };
|
|
31
35
|
};
|
|
32
36
|
|
|
37
|
+
const TOOL_INPUT_INVALID: ToolEnvelopeFailure = Object.freeze({
|
|
38
|
+
success: false,
|
|
39
|
+
error: Object.freeze({
|
|
40
|
+
code: "TOOL_INPUT_INVALID",
|
|
41
|
+
message: "Tool input failed schema validation",
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
const TOOL_OUTPUT_INVALID: ToolEnvelopeFailure = Object.freeze({
|
|
45
|
+
success: false,
|
|
46
|
+
error: Object.freeze({
|
|
47
|
+
code: "TOOL_OUTPUT_INVALID",
|
|
48
|
+
message: "Tool output failed schema validation",
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function readonlyFailure(code: string, message: string): ToolEnvelopeFailure {
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
success: false,
|
|
55
|
+
error: Object.freeze({ code, message }),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function deepFreezeResult<T>(value: T, seen = new WeakSet<object>()): T {
|
|
60
|
+
if (value === null || typeof value !== "object" || seen.has(value))
|
|
61
|
+
return value;
|
|
62
|
+
seen.add(value);
|
|
63
|
+
for (const child of Object.values(value as Record<string, unknown>))
|
|
64
|
+
deepFreezeResult(child, seen);
|
|
65
|
+
return Object.freeze(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
33
68
|
/**
|
|
34
69
|
* Converts anything thrown by a tool into the failure envelope. Never throws.
|
|
35
70
|
* Error-like values with a non-empty string "code" property (HttpError,
|
|
36
71
|
* AgentsError, ComplianceError) keep their code; everything else gets
|
|
37
72
|
* fallbackCode.
|
|
38
73
|
*/
|
|
39
|
-
export function envelopeFailure(
|
|
74
|
+
export function envelopeFailure(
|
|
75
|
+
err: unknown,
|
|
76
|
+
fallbackCode = "TOOL_ERROR",
|
|
77
|
+
): ToolEnvelopeFailure {
|
|
40
78
|
let code = fallbackCode;
|
|
41
79
|
let message = "Unknown error";
|
|
42
80
|
try {
|
|
@@ -60,7 +98,7 @@ export function envelopeFailure(err: unknown, fallbackCode = "TOOL_ERROR"): Tool
|
|
|
60
98
|
} catch {
|
|
61
99
|
// A hostile getter must not break the envelope; keep the fallbacks.
|
|
62
100
|
}
|
|
63
|
-
return
|
|
101
|
+
return readonlyFailure(code, message);
|
|
64
102
|
}
|
|
65
103
|
|
|
66
104
|
// ---------------------------------------------------------------------------
|
|
@@ -81,7 +119,10 @@ export interface TenantToolContext {
|
|
|
81
119
|
* a defineActuarialTool execute the wrapper converts that throw into the
|
|
82
120
|
* failure envelope, so the model sees a recoverable error, never a crash.
|
|
83
121
|
*/
|
|
84
|
-
export function tenantOf(
|
|
122
|
+
export function tenantOf(
|
|
123
|
+
context: TenantToolContext | undefined,
|
|
124
|
+
key = "projectId",
|
|
125
|
+
): string {
|
|
85
126
|
return resolveTenant(context, { source: "request-context", key });
|
|
86
127
|
}
|
|
87
128
|
|
|
@@ -100,7 +141,9 @@ export interface McpContextLike {
|
|
|
100
141
|
* the transport-by-transport story). Returns undefined when no auth info is
|
|
101
142
|
* present — the caller decides that is fatal.
|
|
102
143
|
*/
|
|
103
|
-
export function resolveMcpAuthInfo(
|
|
144
|
+
export function resolveMcpAuthInfo(
|
|
145
|
+
context: McpContextLike | undefined,
|
|
146
|
+
): McpAuthInfoLike | undefined {
|
|
104
147
|
if (!context) return undefined;
|
|
105
148
|
|
|
106
149
|
// Primary: the streamable-HTTP call path passes the transport extra at
|
|
@@ -116,9 +159,11 @@ export function resolveMcpAuthInfo(context: McpContextLike | undefined): McpAuth
|
|
|
116
159
|
| undefined;
|
|
117
160
|
if (proxiedExtra?.authInfo) return proxiedExtra.authInfo;
|
|
118
161
|
|
|
119
|
-
//
|
|
162
|
+
// Lock-tested @mastra/mcp 1.17.3: createProxiedRequestContext copies each
|
|
120
163
|
// extra key onto the RequestContext verbatim, so authInfo is top-level.
|
|
121
|
-
const topLevelAuthInfo = requestContext.get("authInfo") as
|
|
164
|
+
const topLevelAuthInfo = requestContext.get("authInfo") as
|
|
165
|
+
| McpAuthInfoLike
|
|
166
|
+
| undefined;
|
|
122
167
|
if (topLevelAuthInfo) return topLevelAuthInfo;
|
|
123
168
|
}
|
|
124
169
|
return undefined;
|
|
@@ -175,7 +220,9 @@ export function resolveTenant(
|
|
|
175
220
|
const TENANT_KEY_PATTERN = /^(project|tenant)[_-]?id$/i;
|
|
176
221
|
|
|
177
222
|
/** Top-level shape keys of a zod object schema, or null when not an object schema. */
|
|
178
|
-
export function zodObjectShape(
|
|
223
|
+
export function zodObjectShape(
|
|
224
|
+
schema: unknown,
|
|
225
|
+
): Record<string, unknown> | null {
|
|
179
226
|
if (typeof schema !== "object" || schema === null) return null;
|
|
180
227
|
const def = (schema as { _def?: { typeName?: unknown } })._def;
|
|
181
228
|
if (def?.typeName !== "ZodObject") return null;
|
|
@@ -281,7 +328,15 @@ function assertNoTenantKeys(
|
|
|
281
328
|
}
|
|
282
329
|
stack.add(schema);
|
|
283
330
|
try {
|
|
284
|
-
lintSchemaNode(
|
|
331
|
+
lintSchemaNode(
|
|
332
|
+
schema,
|
|
333
|
+
toolId,
|
|
334
|
+
path,
|
|
335
|
+
allowUninspected,
|
|
336
|
+
usedAllowances,
|
|
337
|
+
stack,
|
|
338
|
+
depth,
|
|
339
|
+
);
|
|
285
340
|
} finally {
|
|
286
341
|
stack.delete(schema);
|
|
287
342
|
}
|
|
@@ -322,7 +377,15 @@ function lintSchemaNode(
|
|
|
322
377
|
`Tool "${toolId}" declares input key "${path}.${key}": tenant ids travel only via the server-set RequestContext (read them with tenantOf), never through the model-facing input schema`,
|
|
323
378
|
);
|
|
324
379
|
}
|
|
325
|
-
assertNoTenantKeys(
|
|
380
|
+
assertNoTenantKeys(
|
|
381
|
+
value,
|
|
382
|
+
toolId,
|
|
383
|
+
`${path}.${key}`,
|
|
384
|
+
allowUninspected,
|
|
385
|
+
usedAllowances,
|
|
386
|
+
stack,
|
|
387
|
+
depth + 1,
|
|
388
|
+
);
|
|
326
389
|
}
|
|
327
390
|
return;
|
|
328
391
|
}
|
|
@@ -348,13 +411,21 @@ function lintSchemaNode(
|
|
|
348
411
|
throw new AgentsError(
|
|
349
412
|
"BAD_INPUT_SCHEMA",
|
|
350
413
|
`Tool "${toolId}": the ${typeName} at "${path}" admits values the tenant lint cannot ` +
|
|
351
|
-
|
|
352
|
-
|
|
414
|
+
"inspect. Either declare the shape with typed keys, or — if this input is validated " +
|
|
415
|
+
"downstream (parseDocument etc.) — name the exact path in `allowUninspected` so the " +
|
|
353
416
|
"exception is deliberate and greppable",
|
|
354
417
|
);
|
|
355
418
|
}
|
|
356
419
|
if (typeName === "ZodArray") {
|
|
357
|
-
assertNoTenantKeys(
|
|
420
|
+
assertNoTenantKeys(
|
|
421
|
+
def.type,
|
|
422
|
+
toolId,
|
|
423
|
+
`${path}[]`,
|
|
424
|
+
allowUninspected,
|
|
425
|
+
usedAllowances,
|
|
426
|
+
stack,
|
|
427
|
+
depth + 1,
|
|
428
|
+
);
|
|
358
429
|
return;
|
|
359
430
|
}
|
|
360
431
|
if (
|
|
@@ -364,49 +435,146 @@ function lintSchemaNode(
|
|
|
364
435
|
typeName === "ZodCatch" ||
|
|
365
436
|
typeName === "ZodReadonly"
|
|
366
437
|
) {
|
|
367
|
-
assertNoTenantKeys(
|
|
438
|
+
assertNoTenantKeys(
|
|
439
|
+
def.innerType,
|
|
440
|
+
toolId,
|
|
441
|
+
path,
|
|
442
|
+
allowUninspected,
|
|
443
|
+
usedAllowances,
|
|
444
|
+
stack,
|
|
445
|
+
depth + 1,
|
|
446
|
+
);
|
|
368
447
|
return;
|
|
369
448
|
}
|
|
370
449
|
if (typeName === "ZodPromise" || typeName === "ZodBranded") {
|
|
371
|
-
assertNoTenantKeys(
|
|
450
|
+
assertNoTenantKeys(
|
|
451
|
+
def.type,
|
|
452
|
+
toolId,
|
|
453
|
+
path,
|
|
454
|
+
allowUninspected,
|
|
455
|
+
usedAllowances,
|
|
456
|
+
stack,
|
|
457
|
+
depth + 1,
|
|
458
|
+
);
|
|
372
459
|
return;
|
|
373
460
|
}
|
|
374
461
|
if (typeName === "ZodEffects") {
|
|
375
|
-
assertNoTenantKeys(
|
|
462
|
+
assertNoTenantKeys(
|
|
463
|
+
def.schema,
|
|
464
|
+
toolId,
|
|
465
|
+
path,
|
|
466
|
+
allowUninspected,
|
|
467
|
+
usedAllowances,
|
|
468
|
+
stack,
|
|
469
|
+
depth + 1,
|
|
470
|
+
);
|
|
376
471
|
return;
|
|
377
472
|
}
|
|
378
473
|
if (typeName === "ZodUnion" || typeName === "ZodDiscriminatedUnion") {
|
|
379
|
-
for (const opt of def.options ?? [])
|
|
474
|
+
for (const opt of def.options ?? [])
|
|
475
|
+
assertNoTenantKeys(
|
|
476
|
+
opt,
|
|
477
|
+
toolId,
|
|
478
|
+
path,
|
|
479
|
+
allowUninspected,
|
|
480
|
+
usedAllowances,
|
|
481
|
+
stack,
|
|
482
|
+
depth + 1,
|
|
483
|
+
);
|
|
380
484
|
return;
|
|
381
485
|
}
|
|
382
486
|
if (typeName === "ZodTuple") {
|
|
383
487
|
for (const [index, item] of (def.items ?? []).entries()) {
|
|
384
|
-
assertNoTenantKeys(
|
|
488
|
+
assertNoTenantKeys(
|
|
489
|
+
item,
|
|
490
|
+
toolId,
|
|
491
|
+
`${path}[${index}]`,
|
|
492
|
+
allowUninspected,
|
|
493
|
+
usedAllowances,
|
|
494
|
+
stack,
|
|
495
|
+
depth + 1,
|
|
496
|
+
);
|
|
385
497
|
}
|
|
386
498
|
if (def.rest !== undefined && def.rest !== null) {
|
|
387
|
-
assertNoTenantKeys(
|
|
499
|
+
assertNoTenantKeys(
|
|
500
|
+
def.rest,
|
|
501
|
+
toolId,
|
|
502
|
+
`${path}[rest]`,
|
|
503
|
+
allowUninspected,
|
|
504
|
+
usedAllowances,
|
|
505
|
+
stack,
|
|
506
|
+
depth + 1,
|
|
507
|
+
);
|
|
388
508
|
}
|
|
389
509
|
return;
|
|
390
510
|
}
|
|
391
511
|
if (typeName === "ZodIntersection") {
|
|
392
|
-
assertNoTenantKeys(
|
|
393
|
-
|
|
512
|
+
assertNoTenantKeys(
|
|
513
|
+
def.left,
|
|
514
|
+
toolId,
|
|
515
|
+
path,
|
|
516
|
+
allowUninspected,
|
|
517
|
+
usedAllowances,
|
|
518
|
+
stack,
|
|
519
|
+
depth + 1,
|
|
520
|
+
);
|
|
521
|
+
assertNoTenantKeys(
|
|
522
|
+
def.right,
|
|
523
|
+
toolId,
|
|
524
|
+
path,
|
|
525
|
+
allowUninspected,
|
|
526
|
+
usedAllowances,
|
|
527
|
+
stack,
|
|
528
|
+
depth + 1,
|
|
529
|
+
);
|
|
394
530
|
return;
|
|
395
531
|
}
|
|
396
532
|
if (typeName === "ZodPipeline") {
|
|
397
|
-
assertNoTenantKeys(
|
|
398
|
-
|
|
533
|
+
assertNoTenantKeys(
|
|
534
|
+
def.in,
|
|
535
|
+
toolId,
|
|
536
|
+
path,
|
|
537
|
+
allowUninspected,
|
|
538
|
+
usedAllowances,
|
|
539
|
+
stack,
|
|
540
|
+
depth + 1,
|
|
541
|
+
);
|
|
542
|
+
assertNoTenantKeys(
|
|
543
|
+
def.out,
|
|
544
|
+
toolId,
|
|
545
|
+
path,
|
|
546
|
+
allowUninspected,
|
|
547
|
+
usedAllowances,
|
|
548
|
+
stack,
|
|
549
|
+
depth + 1,
|
|
550
|
+
);
|
|
399
551
|
return;
|
|
400
552
|
}
|
|
401
553
|
if (typeName === "ZodSet") {
|
|
402
|
-
assertNoTenantKeys(
|
|
554
|
+
assertNoTenantKeys(
|
|
555
|
+
def.valueType,
|
|
556
|
+
toolId,
|
|
557
|
+
`${path}[]`,
|
|
558
|
+
allowUninspected,
|
|
559
|
+
usedAllowances,
|
|
560
|
+
stack,
|
|
561
|
+
depth + 1,
|
|
562
|
+
);
|
|
403
563
|
return;
|
|
404
564
|
}
|
|
405
565
|
if (typeName === "ZodLazy") {
|
|
406
566
|
// Resolve once. A self-referential lazy resolves to a node already on the
|
|
407
567
|
// recursion stack and is caught by the cycle guard in assertNoTenantKeys;
|
|
408
568
|
// a generative lazy (fresh node per resolution) hits the frame budget.
|
|
409
|
-
assertNoTenantKeys(
|
|
569
|
+
assertNoTenantKeys(
|
|
570
|
+
def.getter?.(),
|
|
571
|
+
toolId,
|
|
572
|
+
path,
|
|
573
|
+
allowUninspected,
|
|
574
|
+
usedAllowances,
|
|
575
|
+
stack,
|
|
576
|
+
depth + 1,
|
|
577
|
+
);
|
|
410
578
|
return;
|
|
411
579
|
}
|
|
412
580
|
|
|
@@ -433,7 +601,7 @@ export type ActuarialToolKind = "read" | "action";
|
|
|
433
601
|
*/
|
|
434
602
|
export type ActuarialToolContext = TenantToolContext;
|
|
435
603
|
|
|
436
|
-
interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
|
|
604
|
+
interface DefineActuarialToolCommon<TShape extends z.ZodRawShape, TResult> {
|
|
437
605
|
/**
|
|
438
606
|
* Exact schema paths (the lint's dot notation, rooted at "input") where an
|
|
439
607
|
* uninspectable type — z.unknown(), z.any(), z.map() — is INTENTIONAL
|
|
@@ -456,6 +624,15 @@ interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
|
|
|
456
624
|
* AgentsError("TENANT_IN_SCHEMA") at definition time if it does.
|
|
457
625
|
*/
|
|
458
626
|
inputSchema: z.ZodObject<TShape>;
|
|
627
|
+
/**
|
|
628
|
+
* Optional observable-result schema. It must admit the complete success /
|
|
629
|
+
* failure union because validation errors are ordinary tool results.
|
|
630
|
+
*/
|
|
631
|
+
outputSchema?: z.ZodType<
|
|
632
|
+
TResult | ToolEnvelopeFailure,
|
|
633
|
+
z.ZodTypeDef,
|
|
634
|
+
unknown
|
|
635
|
+
>;
|
|
459
636
|
}
|
|
460
637
|
|
|
461
638
|
/**
|
|
@@ -474,7 +651,7 @@ interface DefineActuarialToolCommon<TShape extends z.ZodRawShape> {
|
|
|
474
651
|
* reviewable at the definition site.
|
|
475
652
|
*/
|
|
476
653
|
export type DefineActuarialToolOptions<TShape extends z.ZodRawShape, TResult> =
|
|
477
|
-
| (DefineActuarialToolCommon<TShape> & {
|
|
654
|
+
| (DefineActuarialToolCommon<TShape, TResult> & {
|
|
478
655
|
tenant: "required";
|
|
479
656
|
/** Trusted source for the tenant id. Default "request-context". */
|
|
480
657
|
tenantSource?: TenantSource;
|
|
@@ -487,27 +664,82 @@ export type DefineActuarialToolOptions<TShape extends z.ZodRawShape, TResult> =
|
|
|
487
664
|
* envelope, never an exception.
|
|
488
665
|
*/
|
|
489
666
|
execute: (
|
|
490
|
-
input: z.
|
|
667
|
+
input: z.output<z.ZodObject<TShape>>,
|
|
491
668
|
tenant: string,
|
|
492
669
|
context: ActuarialToolContext,
|
|
493
670
|
) => Promise<TResult>;
|
|
494
671
|
})
|
|
495
|
-
| (DefineActuarialToolCommon<TShape> & {
|
|
672
|
+
| (DefineActuarialToolCommon<TShape, TResult> & {
|
|
496
673
|
tenant: "none";
|
|
497
674
|
execute: (
|
|
498
|
-
input: z.
|
|
675
|
+
input: z.output<z.ZodObject<TShape>>,
|
|
499
676
|
tenant: null,
|
|
500
677
|
context: ActuarialToolContext,
|
|
501
678
|
) => Promise<TResult>;
|
|
502
679
|
});
|
|
503
680
|
|
|
681
|
+
/**
|
|
682
|
+
* A Mastra-compatible tool whose direct execute boundary is fully owned by
|
|
683
|
+
* this SDK. Mastra metadata stays intentionally unknown: the real domain
|
|
684
|
+
* schemas are retained privately so framework validation cannot run their
|
|
685
|
+
* transforms a second time.
|
|
686
|
+
*/
|
|
687
|
+
export type DefinedActuarialTool<TInput, TOutput> = Omit<
|
|
688
|
+
Tool<unknown, unknown>,
|
|
689
|
+
"execute"
|
|
690
|
+
> & {
|
|
691
|
+
readonly kind: ActuarialToolKind;
|
|
692
|
+
execute: (input: TInput, context: ActuarialToolContext) => Promise<TOutput>;
|
|
693
|
+
};
|
|
694
|
+
|
|
695
|
+
function metadataBridge(
|
|
696
|
+
schema: z.ZodTypeAny,
|
|
697
|
+
): StandardSchemaWithJSON<unknown, unknown> {
|
|
698
|
+
const real = toStandardSchema(schema);
|
|
699
|
+
return {
|
|
700
|
+
"~standard": {
|
|
701
|
+
version: 1,
|
|
702
|
+
vendor: "actuarial-ts-metadata-bridge",
|
|
703
|
+
validate: (value: unknown) => ({ value }),
|
|
704
|
+
jsonSchema: {
|
|
705
|
+
input: (options) => real["~standard"].jsonSchema.input(options),
|
|
706
|
+
output: (options) => real["~standard"].jsonSchema.output(options),
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function isFailure(value: unknown): value is ToolEnvelopeFailure {
|
|
713
|
+
if (value === null || typeof value !== "object") return false;
|
|
714
|
+
const candidate = value as { success?: unknown; error?: unknown };
|
|
715
|
+
if (
|
|
716
|
+
candidate.success !== false ||
|
|
717
|
+
candidate.error === null ||
|
|
718
|
+
typeof candidate.error !== "object"
|
|
719
|
+
)
|
|
720
|
+
return false;
|
|
721
|
+
const error = candidate.error as { code?: unknown; message?: unknown };
|
|
722
|
+
return typeof error.code === "string" && typeof error.message === "string";
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function sameJson(left: unknown, right: unknown): boolean {
|
|
726
|
+
try {
|
|
727
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
728
|
+
} catch {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
504
733
|
/**
|
|
505
734
|
* Wraps Mastra's createTool with the envelope + tenant-seam guarantees and
|
|
506
735
|
* tags the result with its kind for toolRegistry classification.
|
|
507
736
|
*/
|
|
508
737
|
export function defineActuarialTool<TShape extends z.ZodRawShape, TResult>(
|
|
509
738
|
options: DefineActuarialToolOptions<TShape, TResult>,
|
|
510
|
-
)
|
|
739
|
+
): DefinedActuarialTool<
|
|
740
|
+
z.input<z.ZodObject<TShape>>,
|
|
741
|
+
TResult | ToolEnvelopeFailure
|
|
742
|
+
> {
|
|
511
743
|
// FAIL CLOSED: a schema the seam cannot inspect is not definable, and the
|
|
512
744
|
// tenant-key lint recurses through every container the model could reach.
|
|
513
745
|
const shape = zodObjectShape(options.inputSchema);
|
|
@@ -519,7 +751,13 @@ export function defineActuarialTool<TShape extends z.ZodRawShape, TResult>(
|
|
|
519
751
|
}
|
|
520
752
|
const allowUninspected = new Set(options.allowUninspected ?? []);
|
|
521
753
|
const usedAllowances = new Set<string>();
|
|
522
|
-
assertNoTenantKeys(
|
|
754
|
+
assertNoTenantKeys(
|
|
755
|
+
options.inputSchema,
|
|
756
|
+
options.id,
|
|
757
|
+
"input",
|
|
758
|
+
allowUninspected,
|
|
759
|
+
usedAllowances,
|
|
760
|
+
);
|
|
523
761
|
for (const declared of allowUninspected) {
|
|
524
762
|
if (!usedAllowances.has(declared)) {
|
|
525
763
|
throw new AgentsError(
|
|
@@ -538,28 +776,87 @@ export function defineActuarialTool<TShape extends z.ZodRawShape, TResult>(
|
|
|
538
776
|
"relationship to the tenant seam explicitly",
|
|
539
777
|
);
|
|
540
778
|
}
|
|
779
|
+
|
|
780
|
+
if (options.outputSchema !== undefined) {
|
|
781
|
+
let probe;
|
|
782
|
+
try {
|
|
783
|
+
probe = options.outputSchema.safeParse(TOOL_OUTPUT_INVALID);
|
|
784
|
+
} catch {
|
|
785
|
+
throw new AgentsError(
|
|
786
|
+
"BAD_OUTPUT_SCHEMA",
|
|
787
|
+
`Tool "${options.id}": outputSchema threw while validating the required failure envelope`,
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
if (!probe.success || !sameJson(probe.data, TOOL_OUTPUT_INVALID)) {
|
|
791
|
+
throw new AgentsError(
|
|
792
|
+
"BAD_OUTPUT_SCHEMA",
|
|
793
|
+
`Tool "${options.id}": outputSchema must preserve the complete tool failure envelope`,
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
541
798
|
const tool = createTool({
|
|
542
799
|
id: options.id,
|
|
543
800
|
description: options.description,
|
|
544
|
-
inputSchema: options.inputSchema,
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// Resolve BEFORE the body runs: an unauthenticated call fails closed
|
|
549
|
-
// here, and execute never sees it.
|
|
550
|
-
const tenant = resolveTenant(context as ActuarialToolContext, {
|
|
551
|
-
source: options.tenantSource,
|
|
552
|
-
key: options.tenantKey,
|
|
553
|
-
});
|
|
554
|
-
return await options.execute(input, tenant, context as ActuarialToolContext);
|
|
555
|
-
}
|
|
556
|
-
return await options.execute(input, null, context as ActuarialToolContext);
|
|
557
|
-
} catch (err) {
|
|
558
|
-
return envelopeFailure(err);
|
|
559
|
-
}
|
|
560
|
-
},
|
|
801
|
+
inputSchema: metadataBridge(options.inputSchema),
|
|
802
|
+
...(options.outputSchema === undefined
|
|
803
|
+
? {}
|
|
804
|
+
: { outputSchema: metadataBridge(options.outputSchema) }),
|
|
561
805
|
});
|
|
562
|
-
|
|
806
|
+
|
|
807
|
+
const execute = async (
|
|
808
|
+
rawInput: z.input<z.ZodObject<TShape>>,
|
|
809
|
+
context: ActuarialToolContext,
|
|
810
|
+
): Promise<TResult | ToolEnvelopeFailure> => {
|
|
811
|
+
let parsedInput;
|
|
812
|
+
try {
|
|
813
|
+
parsedInput = options.inputSchema.safeParse(rawInput);
|
|
814
|
+
} catch {
|
|
815
|
+
return TOOL_INPUT_INVALID;
|
|
816
|
+
}
|
|
817
|
+
if (!parsedInput.success) return TOOL_INPUT_INVALID;
|
|
818
|
+
|
|
819
|
+
let rawOutput: TResult | ToolEnvelopeFailure;
|
|
820
|
+
try {
|
|
821
|
+
if (options.tenant === "required") {
|
|
822
|
+
const tenant = resolveTenant(context, {
|
|
823
|
+
source: options.tenantSource,
|
|
824
|
+
key: options.tenantKey,
|
|
825
|
+
});
|
|
826
|
+
rawOutput = await options.execute(parsedInput.data, tenant, context);
|
|
827
|
+
} else {
|
|
828
|
+
rawOutput = await options.execute(parsedInput.data, null, context);
|
|
829
|
+
}
|
|
830
|
+
} catch (err) {
|
|
831
|
+
rawOutput = envelopeFailure(err);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (options.outputSchema === undefined) {
|
|
835
|
+
return rawOutput === undefined ? TOOL_OUTPUT_INVALID : rawOutput;
|
|
836
|
+
}
|
|
837
|
+
let parsedOutput;
|
|
838
|
+
try {
|
|
839
|
+
parsedOutput = options.outputSchema.safeParse(rawOutput);
|
|
840
|
+
} catch {
|
|
841
|
+
return TOOL_OUTPUT_INVALID;
|
|
842
|
+
}
|
|
843
|
+
if (!parsedOutput.success) return TOOL_OUTPUT_INVALID;
|
|
844
|
+
if (isFailure(rawOutput) && !sameJson(parsedOutput.data, rawOutput))
|
|
845
|
+
return TOOL_OUTPUT_INVALID;
|
|
846
|
+
return deepFreezeResult(parsedOutput.data);
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
// The metadata bridges deliberately erase domain inference on the inherited
|
|
850
|
+
// Mastra surface. This is the single convergence assertion: the adapter
|
|
851
|
+
// above is the only executor and has the exact public input/output contract.
|
|
852
|
+
const defined = Object.assign(tool, {
|
|
853
|
+
execute,
|
|
854
|
+
kind: options.kind,
|
|
855
|
+
}) as DefinedActuarialTool<
|
|
856
|
+
z.input<z.ZodObject<TShape>>,
|
|
857
|
+
TResult | ToolEnvelopeFailure
|
|
858
|
+
>;
|
|
859
|
+
return defined;
|
|
563
860
|
}
|
|
564
861
|
|
|
565
862
|
// ---------------------------------------------------------------------------
|
|
@@ -586,10 +883,10 @@ export interface ActuarialToolRegistry<T extends RegistrableActuarialTool> {
|
|
|
586
883
|
export function toolRegistry<T extends RegistrableActuarialTool>(
|
|
587
884
|
tools: readonly T[],
|
|
588
885
|
): ActuarialToolRegistry<T> {
|
|
589
|
-
const record
|
|
886
|
+
const record = Object.create(null) as Record<string, T>;
|
|
590
887
|
const actionToolIds = new Set<string>();
|
|
591
888
|
for (const tool of tools) {
|
|
592
|
-
if (record
|
|
889
|
+
if (Object.prototype.hasOwnProperty.call(record, tool.id)) {
|
|
593
890
|
throw new AgentsError(
|
|
594
891
|
"DUPLICATE_TOOL_ID",
|
|
595
892
|
`Two tools share the id "${tool.id}"; tool ids must be unique within a registry`,
|