@strav/brain 1.0.0-alpha.14 → 1.0.0-alpha.15
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/package.json +14 -5
- package/src/zod/index.ts +121 -0
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strav/brain",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.15",
|
|
4
4
|
"description": "Strav AI module — unified Provider interface, BrainManager, threads, prompt caching, tools / agents / MCP. Anthropic + OpenAI providers; Gemini / DeepSeek follow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": "./src/index.ts",
|
|
10
|
-
"./mcp": "./src/mcp/index.ts"
|
|
10
|
+
"./mcp": "./src/mcp/index.ts",
|
|
11
|
+
"./zod": "./src/zod/index.ts"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"src",
|
|
@@ -23,11 +24,19 @@
|
|
|
23
24
|
"@anthropic-ai/sdk": "^0.100.0",
|
|
24
25
|
"@google/genai": "^2.7.0",
|
|
25
26
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
26
|
-
"@strav/kernel": "1.0.0-alpha.
|
|
27
|
+
"@strav/kernel": "1.0.0-alpha.15",
|
|
27
28
|
"openai": "^6.0.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"@types/bun": ">=1.3.14"
|
|
31
|
+
"@types/bun": ">=1.3.14",
|
|
32
|
+
"zod": "^4.0.0"
|
|
31
33
|
},
|
|
32
|
-
"
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"zod": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"zod": "^4.4.3"
|
|
41
|
+
}
|
|
33
42
|
}
|
package/src/zod/index.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@strav/brain/zod` — Zod-flavored helpers on top of the
|
|
3
|
+
* schema-library-agnostic core.
|
|
4
|
+
*
|
|
5
|
+
* The default `@strav/brain` import deliberately doesn't depend on
|
|
6
|
+
* Zod — `Tool.inputSchema` and `OutputSchema.jsonSchema` are plain
|
|
7
|
+
* JSON Schema so apps stay free to pick Ajv, Valibot, ArkType, or
|
|
8
|
+
* nothing at all. This sub-path opt-in adds two thin wrappers for
|
|
9
|
+
* apps that already use Zod:
|
|
10
|
+
*
|
|
11
|
+
* - `outputSchema(z, opts?)` turns a Zod schema into an
|
|
12
|
+
* `OutputSchema<z.infer<typeof z>>` — `jsonSchema` is derived
|
|
13
|
+
* via Zod's built-in `z.toJSONSchema`, and `parse` is wired to
|
|
14
|
+
* `z.parse`. Apps then pass the result straight to
|
|
15
|
+
* `BrainManager.generate(input, schema)`.
|
|
16
|
+
*
|
|
17
|
+
* - `tool({ name, description, input, execute })` turns a Zod
|
|
18
|
+
* schema for the tool's input into a framework `Tool` — the
|
|
19
|
+
* wrapper validates the model's raw input through the Zod
|
|
20
|
+
* schema before calling the app's `execute`. Apps get inferred
|
|
21
|
+
* types on `execute(input)` for free.
|
|
22
|
+
*
|
|
23
|
+
* `zod` is an optional peer dependency. Apps that don't use Zod
|
|
24
|
+
* don't install it, don't bundle it, and never import this
|
|
25
|
+
* sub-path — they keep using `defineTool` / hand-written
|
|
26
|
+
* `OutputSchema` literals with raw JSON Schema.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { z } from 'zod'
|
|
30
|
+
import type { OutputSchema } from '../output_schema.ts'
|
|
31
|
+
import type { Tool, ToolContext } from '../tool.ts'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options for `outputSchema`. `name` defaults to `'output'` —
|
|
35
|
+
* apps that surface multiple schemas in logs or to OpenAI's wire
|
|
36
|
+
* format should pass a stable, descriptive identifier.
|
|
37
|
+
*/
|
|
38
|
+
export interface OutputSchemaOptions {
|
|
39
|
+
/** Identifier — defaults to `'output'`. */
|
|
40
|
+
name?: string
|
|
41
|
+
/** Optional model-facing hint. Defaults to the Zod schema's `.describe(…)` if set. */
|
|
42
|
+
description?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Build an `OutputSchema<T>` from a Zod schema. The returned shape
|
|
47
|
+
* is ready to pass to `BrainManager.generate(...)`.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* const CityZ = z.object({ city: z.string(), population: z.number().int() })
|
|
51
|
+
* const { value } = await brain.generate('Capital of France?', outputSchema(CityZ, { name: 'city_answer' }))
|
|
52
|
+
* // ^? { city: string; population: number }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function outputSchema<T>(
|
|
56
|
+
schema: z.ZodType<T>,
|
|
57
|
+
options: OutputSchemaOptions = {},
|
|
58
|
+
): OutputSchema<T> {
|
|
59
|
+
const description = options.description ?? zodDescription(schema)
|
|
60
|
+
const result: OutputSchema<T> = {
|
|
61
|
+
name: options.name ?? 'output',
|
|
62
|
+
jsonSchema: z.toJSONSchema(schema) as Record<string, unknown>,
|
|
63
|
+
parse: (value) => schema.parse(value),
|
|
64
|
+
}
|
|
65
|
+
if (description !== undefined) result.description = description
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Spec passed to `tool(...)`. `execute` receives the model's input
|
|
71
|
+
* already validated + typed against `input` — no need to call
|
|
72
|
+
* `input.parse` manually.
|
|
73
|
+
*/
|
|
74
|
+
export interface ZodToolSpec<TInput, TOutput> {
|
|
75
|
+
name: string
|
|
76
|
+
description: string
|
|
77
|
+
input: z.ZodType<TInput>
|
|
78
|
+
execute(input: TInput, ctx: ToolContext): Promise<TOutput>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build a framework `Tool` from a Zod-typed spec. The wrapper
|
|
83
|
+
* derives `inputSchema` via `z.toJSONSchema` and validates the
|
|
84
|
+
* model's raw input through `input.parse` before delegating to
|
|
85
|
+
* `execute`. Validation failures propagate as `ZodError`; the
|
|
86
|
+
* agentic loop wraps that into a `ToolExecutionError`.
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* const search = tool({
|
|
90
|
+
* name: 'search_orders',
|
|
91
|
+
* description: 'Look up an order by id.',
|
|
92
|
+
* input: z.object({ orderId: z.string() }),
|
|
93
|
+
* async execute({ orderId }, ctx) {
|
|
94
|
+
* // ^? { orderId: string }
|
|
95
|
+
* return await orders.find(orderId, ctx.context)
|
|
96
|
+
* },
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function tool<TInput, TOutput>(
|
|
101
|
+
spec: ZodToolSpec<TInput, TOutput>,
|
|
102
|
+
): Tool<TInput, TOutput> {
|
|
103
|
+
const jsonSchema = z.toJSONSchema(spec.input) as Record<string, unknown>
|
|
104
|
+
return {
|
|
105
|
+
name: spec.name,
|
|
106
|
+
description: spec.description,
|
|
107
|
+
inputSchema: jsonSchema,
|
|
108
|
+
async execute(raw: TInput, ctx: ToolContext): Promise<TOutput> {
|
|
109
|
+
const parsed = spec.input.parse(raw)
|
|
110
|
+
return spec.execute(parsed, ctx)
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function zodDescription(schema: z.ZodType<unknown>): string | undefined {
|
|
116
|
+
// Zod stores `.describe(…)` on the schema's `_def`; surface it
|
|
117
|
+
// as the model-facing hint when callers don't pass one
|
|
118
|
+
// explicitly.
|
|
119
|
+
const def = (schema as unknown as { description?: string }).description
|
|
120
|
+
return typeof def === 'string' && def.length > 0 ? def : undefined
|
|
121
|
+
}
|