@oh-my-pi/pi-ai 17.2.9 → 17.2.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.10] - 2026-08-06
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed the `zod` dependency and `z`/`ZodType` re-exports. Tool schemas now use `omptype` `type()` schemas, with Zod-style authoring still available via `@oh-my-pi/omptype/zod`.
10
+
5
11
  ## [17.2.9] - 2026-08-05
6
12
 
7
13
  ### Fixed
package/README.md CHANGED
@@ -94,21 +94,18 @@ npm install @oh-my-pi/pi-ai
94
94
  ## Quick Start
95
95
 
96
96
  ```typescript
97
- import { z, getModel, stream, complete, Context, Tool } from "@oh-my-pi/pi-ai";
97
+ import { getModel, stream, complete, Context, Tool, type } from "@oh-my-pi/pi-ai";
98
98
 
99
99
  // Fully typed with auto-complete support for both providers and models
100
100
  const model = getModel("openai", "gpt-4o-mini");
101
101
 
102
- // Define tools with Zod schemas for type safety and validation
102
+ // Define tools with omptype schemas for type safety and validation
103
103
  const tools: Tool[] = [
104
104
  {
105
105
  name: "get_time",
106
106
  description: "Get the current time",
107
- parameters: z.object({
108
- timezone: z
109
- .string()
110
- .optional()
111
- .describe("Optional timezone (e.g., America/New_York)"),
107
+ parameters: type({
108
+ "timezone?": type("string").describe("Optional timezone (e.g., America/New_York)"),
112
109
  }),
113
110
  },
114
111
  ];
@@ -221,31 +218,30 @@ for (const block of response.content) {
221
218
 
222
219
  ## Tools
223
220
 
224
- Tools enable LLMs to interact with external systems. This library uses **Zod** schemas for type-safe tool definitions with automatic validation. Schemas are converted to JSON Schema for providers as needed.
221
+ Tools enable LLMs to interact with external systems. Omptype schemas provide type-safe definitions, runtime validation, and JSON Schema conversion for providers.
225
222
 
226
223
  ### Defining Tools
227
224
 
228
225
  ```typescript
229
- import { z, Tool } from "@oh-my-pi/pi-ai";
226
+ import { type Tool, type } from "@oh-my-pi/pi-ai";
230
227
 
231
- // Define tool parameters with Zod
232
228
  const weatherTool: Tool = {
233
229
  name: "get_weather",
234
230
  description: "Get current weather for a location",
235
- parameters: z.object({
236
- location: z.string().describe("City name or coordinates"),
237
- units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
231
+ parameters: type({
232
+ location: type("string").describe("City name or coordinates"),
233
+ units: type.enumerated("celsius", "fahrenheit").default("celsius"),
238
234
  }),
239
235
  };
240
236
 
241
237
  const bookMeetingTool: Tool = {
242
238
  name: "book_meeting",
243
239
  description: "Schedule a meeting",
244
- parameters: z.object({
245
- title: z.string().min(1),
246
- startTime: z.string().describe("ISO 8601 date-time"),
247
- endTime: z.string().describe("ISO 8601 date-time"),
248
- attendees: z.array(z.email()).min(1),
240
+ parameters: type({
241
+ title: type("string").atLeastLength(1),
242
+ startTime: type("string").describe("ISO 8601 date-time"),
243
+ endTime: type("string").describe("ISO 8601 date-time"),
244
+ attendees: type("string.email").array().atLeastLength(1),
249
245
  }),
250
246
  };
251
247
  ```
@@ -345,7 +341,7 @@ for await (const event of s) {
345
341
 
346
342
  ### Validating Tool Arguments
347
343
 
348
- When using `agentLoop`, tool arguments are automatically validated against your Zod parameter schemas before execution. If validation fails, the error is returned to the model as a tool result, allowing it to retry.
344
+ When using `agentLoop`, tool arguments are automatically validated against their omptype schemas before execution. Validation failures are returned to the model as tool results so it can retry.
349
345
 
350
346
  When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
351
347
 
@@ -1,5 +1,4 @@
1
1
  export { type Type, type } from "@oh-my-pi/omptype";
2
- export { type ZodType, z } from "zod/v4";
3
2
  export * from "./api-registry.js";
4
3
  export type * from "./auth-broker/index.js";
5
4
  export type { AuthGatewayBootOptions, ModelResolver } from "./auth-gateway/server.js";
@@ -4,7 +4,6 @@ import type { Type } from "@oh-my-pi/omptype";
4
4
  import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, GrepArgs, GrepResult, LsArgs, LsResult, McpResult, PiBashExecArgs, PiBashExecResult, PiEditExecArgs, PiEditExecResult, PiFindExecArgs, PiFindExecResult, PiGrepExecArgs, PiGrepExecResult, PiLsExecArgs, PiLsExecResult, PiReadExecArgs, PiReadExecResult, PiWriteExecArgs, PiWriteExecResult, ReadArgs, ReadResult, ShellArgs, ShellResult, WriteArgs, WriteResult } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
5
5
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
6
6
  import type { Api, FetchImpl, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
7
- import type { ZodType, z } from "zod/v4";
8
7
  import type { ApiKey } from "./auth-retry.js";
9
8
  import type { BedrockOptions } from "./providers/amazon-bedrock.js";
10
9
  import type { AnthropicOptions } from "./providers/anthropic.js";
@@ -928,12 +927,12 @@ export type TJsonSchema = Record<string, unknown>;
928
927
  /**
929
928
  * Schema type accepted by the {@link Tool} interface.
930
929
  *
931
- * Canonical authoring uses Zod or ArkType. Extension compat may supply a JSON
932
- * Schema object (including TypeBox static schema objects).
930
+ * Canonical authoring uses ArkType. Extension compat may supply a JSON Schema
931
+ * object (including TypeBox static schema objects).
933
932
  */
934
- export type TSchema = ZodType | Type | TJsonSchema;
933
+ export type TSchema = Type | TJsonSchema;
935
934
  /** Resolve parameter types for tool execution / handlers. */
936
- export type Static<S> = S extends ZodType ? z.infer<S> : S extends Type ? S["infer"] : S extends {
935
+ export type Static<S> = S extends Type ? S["infer"] : S extends {
937
936
  static: infer T;
938
937
  } ? T : unknown;
939
938
  export interface ToolCallExample<TArgs = Record<string, unknown>> {
@@ -12,4 +12,3 @@ export * from "./strict-tool-validation.js";
12
12
  export * from "./types.js";
13
13
  export * from "./typescript.js";
14
14
  export * from "./wire.js";
15
- export * from "./zod-decontaminate.js";
@@ -1,76 +1,36 @@
1
1
  /**
2
2
  * Compute the wire (JSON Schema) representation of a tool's parameters.
3
3
  *
4
- * Tools may author parameters in three shapes:
5
- * 1. Zod (canonical) converted to JSON Schema on demand.
6
- * 2. ArkType converted to JSON Schema via its native `toJsonSchema`.
7
- * 3. TypeBox / plain JSON Schema (legacy + extension compat) — upgraded to
8
- * draft 2020-12 without converting.
9
- *
10
- * All three are normalized at the boundary so providers and validators see the same
11
- * JSON Schema dialect.
4
+ * Tools may author parameters as ArkType schemas or legacy TypeBox / plain JSON
5
+ * Schema documents. Both are normalized at the boundary so providers and
6
+ * validators see the same JSON Schema dialect.
12
7
  */
13
8
  import type { Type } from "@oh-my-pi/omptype";
14
- import { type ZodType } from "zod/v4";
15
9
  import type { Tool } from "../../types.js";
16
- /**
17
- * True when `value` is a live Zod schema instance.
18
- *
19
- * The check is stricter than "has a `_zod` property" because a JSON
20
- * round-trip preserves the `_zod` key as a plain object and would otherwise
21
- * fool the predicate — see issue #1101, where MCP servers ship
22
- * `JSON.stringify(zodSchemaInstance)` as a tool's `inputSchema` and the
23
- * resulting plain object then explodes `z.toJSONSchema` because the prototype
24
- * (and every Zod parsing method) is gone.
25
- *
26
- * Live Zod instances always carry a `.parse` function on the prototype;
27
- * impostors do not.
28
- */
29
- export declare function isZodSchema(value: unknown): value is ZodType;
30
10
  /**
31
11
  * True when `value` is a live ArkType schema instance.
32
12
  *
33
13
  * ArkType schemas are callable functions carrying `toJsonSchema`/`assert`
34
- * methods. Zod v4 instances are non-callable objects (keyed off `_zod`), and
35
- * raw JSON Schema is a plain object — the three are disjoint. We deliberately
36
- * avoid the Standard Schema `~standard` marker because Zod v4 implements it too.
14
+ * methods, while raw JSON Schema is a plain object.
37
15
  */
38
16
  export declare function isArkSchema(value: unknown): value is Type;
39
17
  /**
40
- * Normalize `{}` (empty JSON Schema = `z.unknown()` / unconstrained value) to
41
- * boolean `true` in every schema-valued position. JSON Schema draft 2020-12
42
- * §4.3.1: `{}` and `true` are semantically equivalent ("any JSON value").
43
- * Grammar-constrained samplers (llama.cpp, etc.) treat the object form as
44
- * "generate an empty object" rather than "any JSON value", causing open-typed
45
- * fields like `extra.title` (from `z.record(z.string(), z.unknown())`) to
46
- * always emit `{}` instead of the intended string/number/etc. (issue #1179).
18
+ * Normalize `{}` (an unconstrained schema) to boolean `true` in every
19
+ * schema-valued position. JSON Schema draft 2020-12 §4.3.1 defines them as
20
+ * semantically equivalent. Grammar-constrained samplers often treat the object
21
+ * form as "generate an empty object" rather than "any JSON value".
47
22
  *
48
- * Mutates in place. Provider-agnostic applied to every tool wire schema so
49
- * Anthropic, Google, OpenAI, Ollama, Bedrock, and Cursor all see the
50
- * normalized form, regardless of whether the source was Zod or TypeBox.
23
+ * Mutates in place and applies to every tool wire schema.
51
24
  */
52
25
  export declare function normalizeEmptySchemas(node: unknown): void;
53
- /** Convert a Zod schema into the JSON Schema shape providers consume. */
54
- export declare function zodToWireSchema(schema: ZodType): Record<string, unknown>;
55
26
  /**
56
27
  * Convert an ArkType schema into the JSON Schema shape providers consume.
57
- *
58
- * Mirrors {@link zodToWireSchema}: emit draft-2020-12, drop the `$schema`
59
- * metadata, run the JSON-schema post-process (NOT the Zod-only cleanup), then
60
- * close declared objects so the wire is `additionalProperties: false` like Zod.
61
- *
62
- * The `fallback` degrades any un-emittable node (a `.narrow()` predicate or a
63
- * morph) to its underlying base schema instead of throwing — matching Zod,
64
- * whose `.refine()`/`.transform()` likewise never appear in the wire schema.
65
28
  */
66
29
  export declare function arkToWireSchema(schema: Type): Record<string, unknown>;
67
30
  /**
68
31
  * Resolve a tool's parameters to a JSON Schema object suitable for sending
69
- * over the wire. Zod schemas are converted (and cached); legacy TypeBox / raw
70
- * JSON Schema parameters are upgraded to draft 2020-12 (and cached).
71
- *
72
- * Zod schemas also receive Zod-artifact cleanup; both branches normalize
73
- * schema-valued positions and nullable scalar unions.
32
+ * over the wire. ArkType schemas are converted and cached; legacy TypeBox /
33
+ * raw JSON Schema parameters are upgraded to draft 2020-12 and cached.
74
34
  */
75
35
  export declare function toolWireSchema(tool: Tool): Record<string, unknown>;
76
36
  /**
@@ -1,22 +1,10 @@
1
1
  /**
2
2
  * Tool-call argument validation pipeline.
3
3
  *
4
- * Tools may declare their parameters as either Zod schemas (canonical) or
5
- * plain JSON Schema (legacy / extensions). This module is the single
6
- * entrypoint the agent calls before dispatching a tool — it:
7
- *
8
- * 1. Builds (or fetches from cache) a `ValidationContext` for the tool —
9
- * the Zod schema if available plus the equivalent wire JSON Schema, or
10
- * just the JSON Schema for non-Zod tools.
11
- * 2. Normalizes LLM quirks (null / "null" → omit-or-default substitution)
12
- * against the JSON Schema before validation.
13
- * 3. Validates with the Zod or JSON-Schema validator.
14
- * 4. On failure, walks the resulting issues and coerces common LLM type
15
- * drift (JSON-stringified values, boolean/number/string scalar drift),
16
- * drops unrecognized keys, and retries up to `MAX_COERCION_PASSES` times.
17
- * 5. Throws a formatted error if reconciliation fails; otherwise returns
18
- * the parsed arguments with original unknown root fields preserved (so
19
- * hallucinated top-level keys still surface to the caller).
4
+ * Tools may declare ArkType schemas or plain JSON Schema. This module builds a
5
+ * cached validation context, normalizes common LLM quirks against the wire
6
+ * schema, validates, performs conservative schema-directed coercions, and
7
+ * returns parsed arguments while preserving unknown root fields.
20
8
  *
21
9
  * The goal is to be conservative: every coercion is a structural rewrite that
22
10
  * keeps the schema in charge of acceptance — we never invent values, only
@@ -32,10 +20,8 @@ import type { Tool, ToolCall } from "../types.js";
32
20
  */
33
21
  export declare function validateToolCall(tools: Tool[], toolCall: ToolCall): ToolCall["arguments"];
34
22
  /**
35
- * Validates tool call arguments against the tool's schema (Zod or plain JSON
36
- * Schema). Applies LLM-quirk coercions (numeric strings, JSON-string
37
- * containers, null/invalid-empty-string-for-optional, null-for-default) before
38
- * declaring failure.
23
+ * Validates tool call arguments against an ArkType or plain JSON Schema schema.
24
+ * Applies conservative LLM-quirk normalization before declaring failure.
39
25
  *
40
26
  * @throws Error with a formatted message when validation cannot be reconciled.
41
27
  */
@@ -17,6 +17,15 @@ interface OpenAIResponsesReplaySanitizeOptions {
17
17
  supportsImageDetailOriginal?: boolean;
18
18
  supportsComputerUse?: boolean;
19
19
  }
20
+ /**
21
+ * Removes response-only lifecycle status from item types that reject it when replayed as input.
22
+ *
23
+ * Returns the original array when no item needs sanitization.
24
+ */
25
+ export declare function stripOpenAIResponsesOutputOnlyStatusesForReplay<TItem extends {
26
+ type?: unknown;
27
+ status?: unknown;
28
+ }>(items: TItem[]): TItem[];
20
29
  export declare function sanitizeOpenAIResponsesHistoryItemsForReplay(items: Array<Record<string, unknown>>, options?: OpenAIResponsesReplaySanitizeOptions): ResponseInput;
21
30
  /** Strip reasoning IDs whose only linked native output is a computer call that will be demoted. */
22
31
  export declare function stripOpenAIResponsesComputerLinkedReasoningIdsForReplay(items: ResponseInput): ResponseInput;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.2.9",
4
+ "version": "17.2.10",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,11 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/omptype": "17.2.9",
42
- "@oh-my-pi/pi-catalog": "17.2.9",
43
- "@oh-my-pi/pi-utils": "17.2.9",
44
- "@oh-my-pi/pi-wire": "17.2.9",
45
- "zod": "^4"
41
+ "@oh-my-pi/omptype": "17.2.10",
42
+ "@oh-my-pi/pi-catalog": "17.2.10",
43
+ "@oh-my-pi/pi-utils": "17.2.10",
44
+ "@oh-my-pi/pi-wire": "17.2.10"
46
45
  },
47
46
  "devDependencies": {
48
47
  "@bufbuild/protoc-gen-es": "^2.12.1",
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export { type Type, type } from "@oh-my-pi/omptype";
2
- export { type ZodType, z } from "zod/v4";
3
2
  export * from "./api-registry";
4
3
  export type * from "./auth-broker";
5
4
  export type { AuthGatewayBootOptions, ModelResolver } from "./auth-gateway/server";
package/src/types.ts CHANGED
@@ -36,7 +36,6 @@ import type {
36
36
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
37
37
  import { isOpenAIModelId } from "@oh-my-pi/pi-catalog/identity/family";
38
38
  import type { Api, FetchImpl, KnownApi, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
39
- import type { ZodType, z } from "zod/v4";
40
39
  import type { ApiKey } from "./auth-retry";
41
40
  import type { BedrockOptions } from "./providers/amazon-bedrock";
42
41
  import type { AnthropicOptions } from "./providers/anthropic";
@@ -1140,19 +1139,13 @@ export type TJsonSchema = Record<string, unknown>;
1140
1139
  /**
1141
1140
  * Schema type accepted by the {@link Tool} interface.
1142
1141
  *
1143
- * Canonical authoring uses Zod or ArkType. Extension compat may supply a JSON
1144
- * Schema object (including TypeBox static schema objects).
1142
+ * Canonical authoring uses ArkType. Extension compat may supply a JSON Schema
1143
+ * object (including TypeBox static schema objects).
1145
1144
  */
1146
- export type TSchema = ZodType | Type | TJsonSchema;
1145
+ export type TSchema = Type | TJsonSchema;
1147
1146
 
1148
1147
  /** Resolve parameter types for tool execution / handlers. */
1149
- export type Static<S> = S extends ZodType
1150
- ? z.infer<S>
1151
- : S extends Type
1152
- ? S["infer"]
1153
- : S extends { static: infer T }
1154
- ? T
1155
- : unknown;
1148
+ export type Static<S> = S extends Type ? S["infer"] : S extends { static: infer T } ? T : unknown;
1156
1149
 
1157
1150
  export interface ToolCallExample<TArgs = Record<string, unknown>> {
1158
1151
  caption?: string;
@@ -12,4 +12,3 @@ export * from "./strict-tool-validation";
12
12
  export * from "./types";
13
13
  export * from "./typescript";
14
14
  export * from "./wire";
15
- export * from "./zod-decontaminate";
@@ -24,7 +24,6 @@ import { isValidJsonSchema } from "./meta-validator";
24
24
  import { type DescriptionSpillFormat, spillToDescription } from "./spill";
25
25
  import { enter, epochNext, exit, once, stamp } from "./stamps";
26
26
  import { isJsonObject, isJsonObjectEmpty, type JsonObject } from "./types";
27
- import { decontaminateZodInstance } from "./zod-decontaminate";
28
27
 
29
28
  export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners" | "not";
30
29
 
@@ -1069,8 +1068,7 @@ function hasResidualSchemaIncompatibilities(
1069
1068
  }
1070
1069
 
1071
1070
  export function normalizeSchema(value: unknown, options: NormalizeSchemaOptions): unknown {
1072
- const detoxified = decontaminateZodInstance(value);
1073
- const upgraded = upgradeJsonSchemaTo202012(detoxified);
1071
+ const upgraded = upgradeJsonSchemaTo202012(value);
1074
1072
  const dereferenced = dereferenceJsonSchema(upgraded);
1075
1073
  let normalized = normalizeSchemaNode(dereferenced, {
1076
1074
  ...options,
@@ -1,62 +1,21 @@
1
1
  /**
2
2
  * Compute the wire (JSON Schema) representation of a tool's parameters.
3
3
  *
4
- * Tools may author parameters in three shapes:
5
- * 1. Zod (canonical) converted to JSON Schema on demand.
6
- * 2. ArkType converted to JSON Schema via its native `toJsonSchema`.
7
- * 3. TypeBox / plain JSON Schema (legacy + extension compat) — upgraded to
8
- * draft 2020-12 without converting.
9
- *
10
- * All three are normalized at the boundary so providers and validators see the same
11
- * JSON Schema dialect.
4
+ * Tools may author parameters as ArkType schemas or legacy TypeBox / plain JSON
5
+ * Schema documents. Both are normalized at the boundary so providers and
6
+ * validators see the same JSON Schema dialect.
12
7
  */
13
8
 
14
9
  import type { Type } from "@oh-my-pi/omptype";
15
- // We import the Zod *value* (z) for runtime APIs. Marker checks rely on the
16
- // `_zod` symbol that every Zod v4 schema instance carries.
17
- import { type ZodType, z } from "zod/v4";
18
10
  import type { Tool, TSchema } from "../../types";
19
11
  import { upgradeJsonSchemaTo202012 } from "./draft";
20
12
  import { stamp } from "./stamps";
21
13
 
22
- /**
23
- * True when `value` is a live Zod schema instance.
24
- *
25
- * The check is stricter than "has a `_zod` property" because a JSON
26
- * round-trip preserves the `_zod` key as a plain object and would otherwise
27
- * fool the predicate — see issue #1101, where MCP servers ship
28
- * `JSON.stringify(zodSchemaInstance)` as a tool's `inputSchema` and the
29
- * resulting plain object then explodes `z.toJSONSchema` because the prototype
30
- * (and every Zod parsing method) is gone.
31
- *
32
- * Live Zod instances always carry a `.parse` function on the prototype;
33
- * impostors do not.
34
- */
35
- export function isZodSchema(value: unknown): value is ZodType {
36
- return (
37
- typeof value === "object" &&
38
- value !== null &&
39
- // Zod v4 instances expose a `_zod` internal property with a `def` object.
40
- // Tagging on this marker keeps the check stable across Zod minor versions.
41
- // (`_zod` is part of Zod's documented internal contract used by introspection.)
42
- // We avoid checking constructor name because Zod ships multiple variants
43
- // (`ZodObject`, `ZodOptional`, etc.) and a tagged-union style check would
44
- // have to enumerate them all.
45
- "_zod" in value &&
46
- typeof (value as { _zod?: { def?: unknown } })._zod === "object" &&
47
- // Reject JSON-roundtripped objects that kept the `_zod` key but lost the
48
- // prototype. Real instances have `.parse` on the prototype chain.
49
- typeof (value as { parse?: unknown }).parse === "function"
50
- );
51
- }
52
-
53
14
  /**
54
15
  * True when `value` is a live ArkType schema instance.
55
16
  *
56
17
  * ArkType schemas are callable functions carrying `toJsonSchema`/`assert`
57
- * methods. Zod v4 instances are non-callable objects (keyed off `_zod`), and
58
- * raw JSON Schema is a plain object — the three are disjoint. We deliberately
59
- * avoid the Standard Schema `~standard` marker because Zod v4 implements it too.
18
+ * methods, while raw JSON Schema is a plain object.
60
19
  */
61
20
  export function isArkSchema(value: unknown): value is Type {
62
21
  return (
@@ -149,41 +108,17 @@ function arkJsonAstToWire(value: unknown): unknown {
149
108
  }
150
109
 
151
110
  /** Symbol-stamped caches keyed by schema object identity. */
152
- const kZodWireSchema = Symbol("pi.schema.zod.wire");
153
111
  const kJsonWireSchema = Symbol("pi.schema.json.wire");
154
112
  const kArkWireSchema = Symbol("pi.schema.ark.wire");
155
113
  const kStrippedSchema = Symbol("pi.schema.descriptions.stripped");
156
114
 
157
- /**
158
- * Post-process Zod-emitted JSON Schema so it matches the wire shape providers
159
- * already expect from TypeBox-authored tools:
160
- *
161
- * - Drop the `$schema` URL (providers parse the body, not the metadata).
162
- * - Make fields with a `default` non-required (TypeBox/JSON-Schema semantics
163
- * treat defaulted fields as optional; Zod inverts this and keeps them
164
- * required at the input boundary, then materializes the default).
165
- * - Strip the noisy safe-integer bounds Zod injects for `z.number().int()`.
166
- *
167
- * The empty-schema normalization (`{}` → `true`, see `normalizeEmptySchemas`)
168
- * runs separately from `toolWireSchema` so both Zod and TypeBox tools get it.
169
- */
170
- function postProcess(schema: Record<string, unknown>): Record<string, unknown> {
171
- delete schema.$schema;
172
- walk(schema, true);
173
- normalizeArkPropertyComments(schema);
174
- normalizeEmptySchemas(schema);
175
- return schema;
176
- }
177
-
178
115
  function postProcessJsonSchema(schema: Record<string, unknown>): Record<string, unknown> {
179
- walk(schema, false);
116
+ walk(schema);
180
117
  normalizeArkPropertyComments(schema);
181
118
  normalizeEmptySchemas(schema);
182
119
  return schema;
183
120
  }
184
121
 
185
- const SAFE_INTEGER_MAX = Number.MAX_SAFE_INTEGER;
186
- const SAFE_INTEGER_MIN = Number.MIN_SAFE_INTEGER;
187
122
  const NULLABLE_SCALAR_TYPES = new Set(["string", "number", "integer", "boolean"]);
188
123
 
189
124
  const SCHEMA_DEFINING_SIBLING_KEYS = new Set([
@@ -226,11 +161,6 @@ function isNullVariant(schema: Record<string, unknown>): boolean {
226
161
  function isScalarVariant(schema: Record<string, unknown>): schema is Record<string, unknown> & { type: string } {
227
162
  return typeof schema.type === "string" && NULLABLE_SCALAR_TYPES.has(schema.type);
228
163
  }
229
-
230
- function hasIntegerType(type: unknown): boolean {
231
- return type === "integer" || (Array.isArray(type) && type.includes("integer"));
232
- }
233
-
234
164
  function copyNullableScalarConstraints(schema: Record<string, unknown>, scalarVariant: Record<string, unknown>): void {
235
165
  for (const key in scalarVariant) {
236
166
  if (key === "type" || key === "enum" || key === "const" || Object.hasOwn(schema, key)) continue;
@@ -455,9 +385,9 @@ function collapseConstUnionAnyOf(obj: Record<string, unknown>): void {
455
385
  }
456
386
  }
457
387
 
458
- function walk(node: unknown, zodCleanup: boolean): void {
388
+ function walk(node: unknown): void {
459
389
  if (Array.isArray(node)) {
460
- for (const child of node) walk(child, zodCleanup);
390
+ for (const child of node) walk(child);
461
391
  return;
462
392
  }
463
393
  if (!node || typeof node !== "object") return;
@@ -465,48 +395,16 @@ function walk(node: unknown, zodCleanup: boolean): void {
465
395
  rewriteNullableScalarAnyOf(obj);
466
396
  inferBareEnumScalarType(obj);
467
397
  collapseConstUnionAnyOf(obj);
468
-
469
- if (zodCleanup) {
470
- // Drop noise injected for `z.number().int()`.
471
- if (hasIntegerType(obj.type)) {
472
- if (obj.minimum === SAFE_INTEGER_MIN) delete obj.minimum;
473
- if (obj.maximum === SAFE_INTEGER_MAX) delete obj.maximum;
474
- }
475
-
476
- // Make defaulted properties non-required.
477
- if (Array.isArray(obj.required) && obj.properties && typeof obj.properties === "object") {
478
- const properties = obj.properties as Record<string, unknown>;
479
- const required = obj.required as string[];
480
- const filtered = required.filter(name => {
481
- const propertySchema = properties[name];
482
- if (!propertySchema || typeof propertySchema !== "object") return true;
483
- return !("default" in (propertySchema as Record<string, unknown>));
484
- });
485
- if (filtered.length !== required.length) {
486
- if (filtered.length === 0) {
487
- delete obj.required;
488
- } else {
489
- obj.required = filtered;
490
- }
491
- }
492
- }
493
- }
494
-
495
- for (const k in obj) walk(obj[k], zodCleanup);
398
+ for (const k in obj) walk(obj[k]);
496
399
  }
497
400
 
498
401
  /**
499
- * Normalize `{}` (empty JSON Schema = `z.unknown()` / unconstrained value) to
500
- * boolean `true` in every schema-valued position. JSON Schema draft 2020-12
501
- * §4.3.1: `{}` and `true` are semantically equivalent ("any JSON value").
502
- * Grammar-constrained samplers (llama.cpp, etc.) treat the object form as
503
- * "generate an empty object" rather than "any JSON value", causing open-typed
504
- * fields like `extra.title` (from `z.record(z.string(), z.unknown())`) to
505
- * always emit `{}` instead of the intended string/number/etc. (issue #1179).
402
+ * Normalize `{}` (an unconstrained schema) to boolean `true` in every
403
+ * schema-valued position. JSON Schema draft 2020-12 §4.3.1 defines them as
404
+ * semantically equivalent. Grammar-constrained samplers often treat the object
405
+ * form as "generate an empty object" rather than "any JSON value".
506
406
  *
507
- * Mutates in place. Provider-agnostic applied to every tool wire schema so
508
- * Anthropic, Google, OpenAI, Ollama, Bedrock, and Cursor all see the
509
- * normalized form, regardless of whether the source was Zod or TypeBox.
407
+ * Mutates in place and applies to every tool wire schema.
510
408
  */
511
409
  export function normalizeEmptySchemas(node: unknown): void {
512
410
  if (Array.isArray(node)) {
@@ -539,23 +437,10 @@ export function normalizeEmptySchemas(node: unknown): void {
539
437
  for (const k in obj) normalizeEmptySchemas(obj[k]);
540
438
  }
541
439
 
542
- /** Convert a Zod schema into the JSON Schema shape providers consume. */
543
- export function zodToWireSchema(schema: ZodType): Record<string, unknown> {
544
- return stamp(schema, kZodWireSchema, s => {
545
- // `target: "draft-2020-12"` matches what Anthropic's `input_schema` validator
546
- // requires out of the box; our other provider sanitizers (OpenAI strict,
547
- // Google, Anthropic CCA) already handle the superset structurally.
548
- const raw = z.toJSONSchema(s, { target: "draft-2020-12" }) as Record<string, unknown>;
549
- return postProcess(raw);
550
- });
551
- }
552
-
553
440
  /**
554
441
  * Recursively set `additionalProperties: false` on declared object nodes so the
555
- * model-facing wire matches Zod's closed emission. Only nodes that declare
556
- * `properties` and carry neither `additionalProperties` nor `patternProperties`
557
- * are closed — open record/index nodes (which already carry one of those, e.g.
558
- * `additionalProperties: true` after empty-schema normalization) stay open.
442
+ * model-facing wire is closed. Only nodes that declare `properties` and carry
443
+ * neither `additionalProperties` nor `patternProperties` are closed.
559
444
  *
560
445
  * Traverses only schema-valued positions via the shared traversal-key constants
561
446
  * so it never descends into `default`/`examples`/`enum`/`const` instance data.
@@ -665,14 +550,6 @@ function pruneArkUndefinedUnionBranches(node: unknown): void {
665
550
 
666
551
  /**
667
552
  * Convert an ArkType schema into the JSON Schema shape providers consume.
668
- *
669
- * Mirrors {@link zodToWireSchema}: emit draft-2020-12, drop the `$schema`
670
- * metadata, run the JSON-schema post-process (NOT the Zod-only cleanup), then
671
- * close declared objects so the wire is `additionalProperties: false` like Zod.
672
- *
673
- * The `fallback` degrades any un-emittable node (a `.narrow()` predicate or a
674
- * morph) to its underlying base schema instead of throwing — matching Zod,
675
- * whose `.refine()`/`.transform()` likewise never appear in the wire schema.
676
553
  */
677
554
  export function arkToWireSchema(schema: Type): Record<string, unknown> {
678
555
  return stamp(schema, kArkWireSchema, s => {
@@ -687,16 +564,12 @@ export function arkToWireSchema(schema: Type): Record<string, unknown> {
687
564
 
688
565
  /**
689
566
  * Resolve a tool's parameters to a JSON Schema object suitable for sending
690
- * over the wire. Zod schemas are converted (and cached); legacy TypeBox / raw
691
- * JSON Schema parameters are upgraded to draft 2020-12 (and cached).
692
- *
693
- * Zod schemas also receive Zod-artifact cleanup; both branches normalize
694
- * schema-valued positions and nullable scalar unions.
567
+ * over the wire. ArkType schemas are converted and cached; legacy TypeBox /
568
+ * raw JSON Schema parameters are upgraded to draft 2020-12 and cached.
695
569
  */
696
570
  export function toolWireSchema(tool: Tool): Record<string, unknown> {
697
571
  const params: TSchema = tool.parameters;
698
572
  if (isArkSchema(params)) return arkToWireSchema(params);
699
- if (isZodSchema(params)) return zodToWireSchema(params);
700
573
  return stamp(params as Record<string, unknown>, kJsonWireSchema, p => {
701
574
  const raw = isArkJsonAst(p) ? arkJsonAstToWire(p) : p;
702
575
  const upgraded = upgradeJsonSchemaTo202012(raw) as Record<string, unknown>;