@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.
Files changed (2) hide show
  1. package/package.json +14 -5
  2. 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.14",
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.14",
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
- "devDependencies": null
34
+ "peerDependenciesMeta": {
35
+ "zod": {
36
+ "optional": true
37
+ }
38
+ },
39
+ "devDependencies": {
40
+ "zod": "^4.4.3"
41
+ }
33
42
  }
@@ -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
+ }