@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 +43 -0
- package/README.md +20 -17
- package/package.json +3 -6
- package/src/index.ts +2 -2
- package/src/providers/amazon-bedrock.ts +7 -1
- package/src/providers/anthropic.ts +3 -3
- package/src/providers/azure-openai-responses.ts +2 -1
- package/src/providers/cursor.ts +2 -1
- package/src/providers/google-shared.ts +3 -3
- package/src/providers/mock.ts +469 -0
- package/src/providers/ollama.ts +3 -3
- package/src/providers/openai-codex-responses.ts +9 -9
- package/src/providers/openai-completions.ts +135 -10
- package/src/providers/openai-responses.ts +17 -22
- package/src/providers/register-builtins.ts +6 -0
- package/src/types.ts +18 -1
- package/src/utils/discovery/antigravity.ts +1 -1
- package/src/utils/discovery/codex.ts +1 -1
- package/src/utils/discovery/cursor.ts +1 -1
- package/src/utils/discovery/gemini.ts +1 -1
- package/src/utils/discovery/openai-compatible.ts +1 -1
- package/src/utils/idle-iterator.ts +7 -2
- package/src/utils/schema/compatibility.ts +10 -27
- package/src/utils/schema/fields.ts +10 -2
- package/src/utils/schema/index.ts +3 -0
- package/src/utils/schema/json-schema-validator.ts +564 -0
- package/src/utils/schema/meta-validator.ts +171 -0
- package/src/utils/schema/normalize-cca.ts +5 -27
- package/src/utils/schema/strict-mode.ts +22 -10
- package/src/utils/schema/wire.ts +115 -0
- package/src/utils/tool-call-healing.ts +271 -0
- package/src/utils/validation.ts +344 -117
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 {
|
|
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
|
|
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:
|
|
103
|
-
timezone:
|
|
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
|
|
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 {
|
|
224
|
+
import { z, Tool, StringEnum } from "@oh-my-pi/pi-ai";
|
|
222
225
|
|
|
223
|
-
// Define tool parameters with
|
|
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:
|
|
228
|
-
location:
|
|
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
|
|
234
|
-
//
|
|
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:
|
|
240
|
-
title:
|
|
241
|
-
startTime:
|
|
242
|
-
endTime:
|
|
243
|
-
attendees:
|
|
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
|
|
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.
|
|
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.
|
|
50
|
-
"@oh-my-pi/pi-utils": "15.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
334
|
+
parameters: sanitizeSchemaForOpenAIResponses(toolWireSchema(tool)),
|
|
334
335
|
strict: false,
|
|
335
336
|
}));
|
|
336
337
|
}
|
package/src/providers/cursor.ts
CHANGED
|
@@ -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
|
|
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
|
|
344
|
-
: { parametersJsonSchema: tool
|
|
343
|
+
? { parameters: prepareSchemaForCCA(toolWireSchema(tool)) }
|
|
344
|
+
: { parametersJsonSchema: toolWireSchema(tool) }),
|
|
345
345
|
})),
|
|
346
346
|
},
|
|
347
347
|
];
|