@oh-my-pi/pi-ai 15.0.2 → 15.1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.1.0] - 2026-05-15
6
+
7
+ ### Breaking Changes
8
+
9
+ - Removed TypeBox root exports (`Type`, `Static`, and `TSchema`) from the package entrypoint, so callers importing those symbols from `@oh-my-pi/pi-ai` must migrate to `zod` or `@oh-my-pi/pi-ai/types`
10
+
11
+ ### Added
12
+
13
+ - Added support for defining tool schemas with Zod (`z.object`, `z.string`, etc.) by allowing `Tool.parameters` to be either Zod schemas or legacy JSON Schema objects and converting them to provider wire format automatically
14
+ - Added package-level schema helpers in the `zod/v4` style by exporting `z` and `ZodType` from the root entrypoint
15
+ - Added a `mock` API provider via `createMockModel` to build `Model<"mock">` instances for fully in-memory, deterministic assistant streams in tests
16
+ - Added `streamMock` and `registerMockApi` so mock responses can be consumed through `stream()` and the global custom API registry without an external model backend
17
+ - Added async/sync response scripting with optional context-based handlers, and new `push()`/`reset()` controls to drive multi-turn mock interactions and inspect per-call invocation state
18
+ - Added support in mock responses for simulating tool calls, usage metadata, custom stop reasons, delayed emissions, and terminal error/aborted outcomes
19
+
20
+ ### Changed
21
+
22
+ - Changed Azure OpenAI Responses tool schema conversion to sanitize tool parameter schemas and rewrite `oneOf` branches as `anyOf` so tool calls remain compatible with Azure's schema expectations
23
+ - Changed `Static<S>` to extract a schema object’s `static` type when present, improving inferred tool argument types for non-Zod parameter definitions
24
+ - Changed `Static` typing behavior so it now infers argument types from Zod schemas and defaults to `unknown` for non-Zod JSON Schema parameter definitions
25
+ - Restored the default steady-state stream idle timeout to 120s (regressed in 15.0.0). 30s was too aggressive for reasoning models, slow proxies, and tool-call planning gaps, surfacing as repeated `Provider stream stalled while waiting for the next event` errors. Existing `PI_STREAM_IDLE_TIMEOUT_MS` / `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` overrides are unchanged.
26
+
27
+ ### Fixed
28
+
29
+ - Preserved top-level unknown fields in validated tool-call arguments so extra root properties are retained after schema coercion
30
+ - Fixed coercion for Zod `record` fields by parsing JSON-stringified record arguments into objects
31
+ - Validated legacy draft-07 JSON Schema tool parameters directly instead of converting through Zod, improving support for features like `$ref`, `definitions`, `nullable`, and `uniqueItems`
32
+ - Fixed Cloud Code Assist schema preparation to strip unsupported `propertyNames` and fall back to a minimal tool schema when schema meta-validation detects malformed keywords
33
+ - Fixed OpenAI Completions streaming to avoid treating non-output chunks (including role-only preambles) as progress events so idle-timeout watchdog behavior no longer hangs on no-op streamed chunks
34
+ - Fixed Cloud Code Assist schema compatibility checks by replacing strict AJV meta-schema validation with structural JSON Schema validation to avoid rejecting structurally valid tool schemas
35
+ - Fixed lazy built-in provider streams (`anthropic-messages`, `bedrock-converse-stream`, `cursor-agent`, `google-*`, `ollama-chat`, `openai-*`) prematurely aborting slow first-token responses with `Provider stream stalled while waiting for the next event`. The lazy-stream watchdog wrapper was treating the synthetic `start` event (yielded immediately by every provider before the model emits any tokens) as the first real item, which caused the watchdog to drop from `firstItemTimeoutMs` (100s) to `idleTimeoutMs` (30s) before the upstream model had produced anything. The shared `iterateWithIdleTimeout` now keeps `awaitingFirstItem` true until a real progress item arrives, and the lazy-stream wrapper marks `start` as a non-progress keepalive ([#1073](https://github.com/can1357/oh-my-pi/pull/1073) regression).
36
+ - Heal leaked Kimi K2 chat-template tool-call tokens (`<|tool_calls_section_begin|>` … `<|tool_call_argument_begin|>` … `<|tool_calls_section_end|>`) that some hosts (native `kimi-code` API, OpenRouter, Fireworks, etc.) emit into `delta.content` instead of structured `tool_calls`. The OpenAI-completions stream consumer now strips the markers from visible text, reconstructs the embedded calls as proper `toolCall` content blocks (stream-aware, token-boundary-safe), and promotes `finish_reason: stop` to `toolUse` when calls were healed.
37
+ - Fixed OpenAI-completions Kimi K2 healed-call promotion clobbering non-stop terminal finish reasons (`error`, `length`, `aborted`); promotion now only fires when the prior stop reason is the natural-completion `stop`
38
+ - Fixed OpenAI-completions duplicate Kimi tool calls when a single chunk delivers both leaked markers and a structured `delta.tool_calls`; the healer now strips visible markers but discards its synthesized calls so structured payloads remain the single source of truth
39
+ - Fixed Kimi tool-call healer synthesizing a bogus empty call when assistant text mentions a literal `<|tool_call_end|>` (or `<|tool_call_begin|>` / `<|tool_call_argument_begin|>`) outside an active `<|tool_calls_section_begin|>…<|tool_calls_section_end|>` section; the tokens now survive as text
40
+ - Fixed OpenAI-completions ignoring per-request `StreamOptions.streamFirstEventTimeoutMs` when configuring the underlying OpenAI SDK HTTP timeout, causing slow-before-headers providers to be aborted at the env default before the wrapping watchdog armed
41
+ - Fixed JSON Schema validator silently accepting values that violate `propertyNames`, `patternProperties`, `dependentRequired`, `dependencies`, `if`/`then`/`else`, `contains`, and `prefixItems`; the in-tree validator now enforces these keywords instead of falling through. `unevaluatedProperties`/`unevaluatedItems` remain permissive but log a one-time warning so tool authors are not surprised.
42
+ - Fixed recursive `$ref` schemas being treated as universally valid: the validator previously short-circuited on the second occurrence of any ref it had already seen, so nested values violating the referenced sub-schema passed. Cycle detection now keys on (ref, value-identity) pairs with a depth cap for primitive values, so genuine sub-tree violations are still caught.
43
+ - Fixed JSON Schema meta-validator accepting malformed `if`/`then`/`else` and `dependencies` keywords; each conditional sub-schema is now structurally validated and draft-07 `dependencies` accepts either a schema or a string array of dependent keys.
44
+ - Fixed Zod-emitted wire schemas dropping null-valued unknown root fields before `preserveUnknownRootFields` could snapshot them, so callers like `task.simple` no longer lose a `schema: null` argument and downstream rejection paths fire as intended.
45
+ - Fixed mock provider partial `Usage` to recompute `totalTokens` (and `cost.total` when cost components are supplied) when omitted, instead of reporting 0
46
+ - Fixed mock provider auto-generated tool-call IDs to use a per-instance counter (now reset by `reset()`), so test order no longer affects IDs across `createMockModel()` instances
47
+
5
48
  ## [15.0.2] - 2026-05-15
6
49
  ### Fixed
7
50
 
package/README.md CHANGED
@@ -89,18 +89,21 @@ npm install @oh-my-pi/pi-ai
89
89
  ## Quick Start
90
90
 
91
91
  ```typescript
92
- import { Type, getModel, stream, complete, Context, Tool, StringEnum } from "@oh-my-pi/pi-ai";
92
+ import { z, getModel, stream, complete, Context, Tool, StringEnum } from "@oh-my-pi/pi-ai";
93
93
 
94
94
  // Fully typed with auto-complete support for both providers and models
95
95
  const model = getModel("openai", "gpt-4o-mini");
96
96
 
97
- // Define tools with TypeBox schemas for type safety and validation
97
+ // Define tools with Zod schemas for type safety and validation
98
98
  const tools: Tool[] = [
99
99
  {
100
100
  name: "get_time",
101
101
  description: "Get the current time",
102
- parameters: Type.Object({
103
- timezone: Type.Optional(Type.String({ description: "Optional timezone (e.g., America/New_York)" })),
102
+ parameters: z.object({
103
+ timezone: z
104
+ .string()
105
+ .optional()
106
+ .describe("Optional timezone (e.g., America/New_York)"),
104
107
  }),
105
108
  },
106
109
  ];
@@ -213,34 +216,34 @@ for (const block of response.content) {
213
216
 
214
217
  ## Tools
215
218
 
216
- Tools enable LLMs to interact with external systems. This library uses TypeBox schemas for type-safe tool definitions with automatic validation using AJV. TypeBox schemas can be serialized and deserialized as plain JSON, making them ideal for distributed systems.
219
+ 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.
217
220
 
218
221
  ### Defining Tools
219
222
 
220
223
  ```typescript
221
- import { Type, Tool, StringEnum } from "@oh-my-pi/pi-ai";
224
+ import { z, Tool, StringEnum } from "@oh-my-pi/pi-ai";
222
225
 
223
- // Define tool parameters with TypeBox
226
+ // Define tool parameters with Zod
224
227
  const weatherTool: Tool = {
225
228
  name: "get_weather",
226
229
  description: "Get current weather for a location",
227
- parameters: Type.Object({
228
- location: Type.String({ description: "City name or coordinates" }),
230
+ parameters: z.object({
231
+ location: z.string().describe("City name or coordinates"),
229
232
  units: StringEnum(["celsius", "fahrenheit"], { default: "celsius" }),
230
233
  }),
231
234
  };
232
235
 
233
- // Note: For Google API compatibility, use StringEnum helper instead of Type.Enum
234
- // Type.Enum generates anyOf/const patterns that Google doesn't support
236
+ // Note: For Google API compatibility, use the StringEnum helper instead of z.enum alone
237
+ // when you need wire-compatible { type: "string", enum: [...] } shapes.
235
238
 
236
239
  const bookMeetingTool: Tool = {
237
240
  name: "book_meeting",
238
241
  description: "Schedule a meeting",
239
- parameters: Type.Object({
240
- title: Type.String({ minLength: 1 }),
241
- startTime: Type.String({ format: "date-time" }),
242
- endTime: Type.String({ format: "date-time" }),
243
- attendees: Type.Array(Type.String({ format: "email" }), { minItems: 1 }),
242
+ parameters: z.object({
243
+ title: z.string().min(1),
244
+ startTime: z.string().describe("ISO 8601 date-time"),
245
+ endTime: z.string().describe("ISO 8601 date-time"),
246
+ attendees: z.array(z.email()).min(1),
244
247
  }),
245
248
  };
246
249
  ```
@@ -340,7 +343,7 @@ for await (const event of s) {
340
343
 
341
344
  ### Validating Tool Arguments
342
345
 
343
- When using `agentLoop`, tool arguments are automatically validated against your TypeBox schemas before execution. If validation fails, the error is returned to the model as a tool result, allowing it to retry.
346
+ 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
347
 
345
348
  When implementing your own tool execution loop with `stream()` or `complete()`, use `validateToolCall` to validate arguments before passing them to your tools:
346
349
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "15.0.2",
4
+ "version": "15.1.1",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,9 @@
46
46
  "@aws-sdk/credential-provider-node": "^3.972.39",
47
47
  "@bufbuild/protobuf": "^2.12.0",
48
48
  "@google/genai": "^1.52.0",
49
- "@oh-my-pi/pi-natives": "15.0.2",
50
- "@oh-my-pi/pi-utils": "15.0.2",
51
- "@sinclair/typebox": "^0.34.49",
49
+ "@oh-my-pi/pi-natives": "15.1.1",
50
+ "@oh-my-pi/pi-utils": "15.1.1",
52
51
  "@smithy/node-http-handler": "^4.6.1",
53
- "ajv": "^8.20.0",
54
- "ajv-formats": "^3.0.1",
55
52
  "openai": "^6.36.0",
56
53
  "partial-json": "^0.1.7",
57
54
  "proxy-agent": "^8.0.1",
package/src/index.ts CHANGED
@@ -1,5 +1,4 @@
1
- export type { Static, TSchema } from "@sinclair/typebox";
2
- export { Type } from "@sinclair/typebox";
1
+ export { type ZodType, z } from "zod/v4";
3
2
  export * from "./api-registry";
4
3
  export * from "./auth-storage";
5
4
  export * from "./model-cache";
@@ -17,6 +16,7 @@ export type * from "./providers/google-gemini-cli";
17
16
  export * from "./providers/google-gemini-headers";
18
17
  export type * from "./providers/google-vertex";
19
18
  export * from "./providers/kimi";
19
+ export * from "./providers/mock";
20
20
  export * from "./providers/ollama";
21
21
  export * from "./providers/openai-codex-responses";
22
22
  export * from "./providers/openai-completions";
@@ -46,6 +46,7 @@ import { normalizeToolCallId, resolveCacheRetention } from "../utils";
46
46
  import { AssistantMessageEventStream } from "../utils/event-stream";
47
47
  import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
48
48
  import { parseStreamingJson } from "../utils/json-parse";
49
+ import { toolWireSchema } from "../utils/schema/wire";
49
50
  import { transformMessages } from "./transform-messages";
50
51
 
51
52
  export interface BedrockOptions extends StreamOptions {
@@ -668,7 +669,12 @@ function convertToolConfig(
668
669
  toolSpec: {
669
670
  name: tool.name,
670
671
  description: tool.description || "",
671
- inputSchema: { json: tool.parameters },
672
+ // Wire schema is structurally a JSON Schema document; the Bedrock SDK
673
+ // types it as the recursive `DocumentType` from `@smithy/types`, which
674
+ // `Record<string, unknown>` does not directly satisfy at the type
675
+ // level. Cast through `unknown` so the actual JSON value passes the
676
+ // type checker without changing runtime behavior.
677
+ inputSchema: { json: toolWireSchema(tool) as unknown as Record<string, never> },
672
678
  },
673
679
  }));
674
680
 
@@ -58,7 +58,7 @@ import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
58
58
  import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
59
59
  import { notifyProviderResponse } from "../utils/provider-response";
60
60
  import { isCopilotTransientModelError } from "../utils/retry";
61
- import { COMBINATOR_KEYS, NO_STRICT } from "../utils/schema";
61
+ import { COMBINATOR_KEYS, NO_STRICT, toolWireSchema } from "../utils/schema";
62
62
  import { notifyRawSseEvent, wrapFetchForSseDebug } from "../utils/sse-debug";
63
63
  import {
64
64
  buildCopilotDynamicHeaders,
@@ -2072,7 +2072,7 @@ export function convertAnthropicMessages(
2072
2072
  return params;
2073
2073
  }
2074
2074
 
2075
- const ANTHROPIC_UNSUPPORTED_TOOL_SCHEMA_FIELDS = new Set(["maxItems", "patternProperties"]);
2075
+ const ANTHROPIC_UNSUPPORTED_TOOL_SCHEMA_FIELDS = new Set(["maxItems", "patternProperties", "propertyNames"]);
2076
2076
  const ANTHROPIC_STRICT_TOOL_ALLOWLIST = new Set(["bash", "python", "edit", "find"]);
2077
2077
  const MAX_ANTHROPIC_STRICT_TOOLS = 20;
2078
2078
  const MAX_ANTHROPIC_STRICT_OPTIONAL_PARAMETERS = 24;
@@ -2314,7 +2314,7 @@ function normalizeAnthropicStrictSchema(
2314
2314
  }
2315
2315
 
2316
2316
  function buildAnthropicBaseToolInputSchema(tool: Tool): Record<string, unknown> {
2317
- const jsonSchema = tool.parameters as Record<string, unknown>;
2317
+ const jsonSchema = toolWireSchema(tool);
2318
2318
  return normalizeAnthropicToolSchema({
2319
2319
  ...jsonSchema,
2320
2320
  type: "object",
@@ -26,6 +26,7 @@ import {
26
26
  getStreamFirstEventTimeoutMs,
27
27
  iterateWithIdleTimeout,
28
28
  } from "../utils/idle-iterator";
29
+ import { sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
29
30
  import { wrapFetchForSseDebug } from "../utils/sse-debug";
30
31
  import { mapToOpenAIResponsesToolChoice } from "../utils/tool-choice";
31
32
  import { normalizeOpenAIResponsesPromptCacheKey, supportsDeveloperRole } from "./openai-responses";
@@ -330,7 +331,7 @@ function convertTools(tools: Tool[]): OpenAITool[] {
330
331
  type: "function",
331
332
  name: tool.name,
332
333
  description: tool.description || "",
333
- parameters: tool.parameters as Record<string, unknown>,
334
+ parameters: sanitizeSchemaForOpenAIResponses(toolWireSchema(tool)),
334
335
  strict: false,
335
336
  }));
336
337
  }
@@ -30,6 +30,7 @@ import { normalizeSystemPrompts } from "../utils";
30
30
  import { AssistantMessageEventStream } from "../utils/event-stream";
31
31
  import { parseStreamingJson } from "../utils/json-parse";
32
32
  import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
33
+ import { toolWireSchema } from "../utils/schema/wire";
33
34
  import type { McpToolDefinition } from "./cursor/gen/agent_pb";
34
35
  import {
35
36
  AgentClientMessageSchema,
@@ -2067,7 +2068,7 @@ function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[]
2067
2068
  }
2068
2069
 
2069
2070
  return advertisedTools.map(tool => {
2070
- const jsonSchema = tool.parameters as Record<string, unknown> | undefined;
2071
+ const jsonSchema = toolWireSchema(tool);
2071
2072
  const schemaValue: JsonValue =
2072
2073
  jsonSchema && typeof jsonSchema === "object"
2073
2074
  ? (jsonSchema as JsonValue)
@@ -30,7 +30,7 @@ import type {
30
30
  import { normalizeSystemPrompts } from "../utils";
31
31
  import { AssistantMessageEventStream } from "../utils/event-stream";
32
32
  import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
33
- import { prepareSchemaForCCA, sanitizeSchemaForGoogle } from "../utils/schema";
33
+ import { prepareSchemaForCCA, sanitizeSchemaForGoogle, toolWireSchema } from "../utils/schema";
34
34
  import { transformMessages } from "./transform-messages";
35
35
  import { NON_VISION_IMAGE_PLACEHOLDER } from "./vision-guard";
36
36
 
@@ -340,8 +340,8 @@ export function convertTools(
340
340
  name: tool.name,
341
341
  description: tool.description || "",
342
342
  ...(useParameters
343
- ? { parameters: prepareSchemaForCCA(tool.parameters) }
344
- : { parametersJsonSchema: tool.parameters }),
343
+ ? { parameters: prepareSchemaForCCA(toolWireSchema(tool)) }
344
+ : { parametersJsonSchema: toolWireSchema(tool) }),
345
345
  })),
346
346
  },
347
347
  ];