@oh-my-pi/pi-ai 17.2.9 → 17.2.11
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 +24 -0
- package/README.md +15 -19
- package/dist/types/error/flags.d.ts +4 -4
- package/dist/types/index.d.ts +0 -1
- package/dist/types/registry/oauth/cursor.d.ts +1 -0
- package/dist/types/registry/oauth/index.d.ts +4 -0
- package/dist/types/types.d.ts +14 -7
- package/dist/types/usage/cursor.d.ts +1 -0
- package/dist/types/utils/http-inspector.d.ts +4 -5
- package/dist/types/utils/retry.d.ts +2 -3
- package/dist/types/utils/schema/index.d.ts +0 -1
- package/dist/types/utils/schema/wire.d.ts +11 -51
- package/dist/types/utils/validation.d.ts +6 -20
- package/dist/types/utils.d.ts +9 -0
- package/package.json +5 -6
- package/src/auth/sqlite-credential-store.ts +17 -0
- package/src/error/flags.ts +13 -15
- package/src/error/rate-limit.ts +53 -1
- package/src/index.ts +0 -1
- package/src/providers/anthropic.ts +14 -4
- package/src/providers/register-builtins.ts +15 -2
- package/src/registry/oauth/cursor.ts +29 -13
- package/src/registry/oauth/index.ts +7 -0
- package/src/stream.ts +12 -4
- package/src/types.ts +28 -25
- package/src/usage/cursor.ts +181 -38
- package/src/utils/http-inspector.ts +4 -5
- package/src/utils/retry.ts +2 -3
- package/src/utils/schema/index.ts +0 -1
- package/src/utils/schema/normalize.ts +1 -3
- package/src/utils/schema/wire.ts +17 -144
- package/src/utils/validation.ts +25 -154
- package/src/utils.ts +30 -10
- package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
- package/src/utils/schema/zod-decontaminate.ts +0 -331
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.2.11] - 2026-08-07
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Fixed handling of GitHub Copilot's model_not_available_for_integrator error to prevent unnecessary retries, preserving the actionable available models list.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added support for reporting Cursor personal monthly USD quotas and remaining balances, labeled by verified profile email accounts.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed an issue where ANTHROPIC_BASE_URL was ignored for Anthropic chat requests, ensuring requests are routed to the configured host and forwarding ANTHROPIC_CUSTOM_HEADERS to non-official gateways.
|
|
18
|
+
- Fixed an issue where a legacy pre-organization login credential could persist and cause a permanent error row in omp usage even after a successful organization-scoped re-login.
|
|
19
|
+
- Fixed an issue where lazy provider streams (including Amazon Bedrock, Google, Cursor, Devin, and Ollama) ignored model-specific idle timeouts, which previously caused healthy but slow reasoning turns to prematurely time out.
|
|
20
|
+
- Improved error classification for Simplified Chinese quota-exhaustion and rate-limit messages, ensuring affected credentials are correctly rotated or backed off instead of being treated as unknown errors.
|
|
21
|
+
- Classified subscription and plan-cap 429 responses as rotatable usage limits rather than transient rate-limit throttles, enabling smoother credential rotation.
|
|
22
|
+
|
|
23
|
+
## [17.2.10] - 2026-08-06
|
|
24
|
+
|
|
25
|
+
### Breaking Changes
|
|
26
|
+
|
|
27
|
+
- 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`.
|
|
28
|
+
|
|
5
29
|
## [17.2.9] - 2026-08-05
|
|
6
30
|
|
|
7
31
|
### 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 {
|
|
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
|
|
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:
|
|
108
|
-
timezone:
|
|
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.
|
|
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 {
|
|
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:
|
|
236
|
-
location:
|
|
237
|
-
units:
|
|
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:
|
|
245
|
-
title:
|
|
246
|
-
startTime:
|
|
247
|
-
endTime:
|
|
248
|
-
attendees:
|
|
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
|
|
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
|
|
|
@@ -65,10 +65,10 @@ export declare function isGrammarError(error: unknown): boolean;
|
|
|
65
65
|
*/
|
|
66
66
|
export declare function isFastModeUnsupported(error: unknown): boolean;
|
|
67
67
|
/**
|
|
68
|
-
* GitHub Copilot 400
|
|
69
|
-
* transient fleet skew, not a malformed request. Reads the
|
|
70
|
-
* through the SDK/body envelopes, then falls back to the
|
|
71
|
-
* SDK families put in `message
|
|
68
|
+
* GitHub Copilot 400 `model_not_supported` response for a model advertised by
|
|
69
|
+
* `/models` — transient fleet skew, not a malformed request. Reads the
|
|
70
|
+
* structural `code` through the SDK/body envelopes, then falls back to the
|
|
71
|
+
* stringified body both SDK families put in `message`.
|
|
72
72
|
*/
|
|
73
73
|
export declare function isCopilotTransientModelError(error: unknown): boolean;
|
|
74
74
|
export declare function classifyMessage(message: {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -12,4 +12,5 @@ export declare function pollCursorAuth(uuid: string, verifier: string): Promise<
|
|
|
12
12
|
}>;
|
|
13
13
|
export declare function loginCursor(onAuthUrl: (url: string) => void, onPollStart?: () => void): Promise<OAuthCredentials>;
|
|
14
14
|
export declare function refreshCursorToken(apiKeyOrRefreshToken: string): Promise<OAuthCredentials>;
|
|
15
|
+
export declare function extractCursorAccessTokenUserId(accessToken: string): string | undefined;
|
|
15
16
|
export declare function isCursorTokenExpiringSoon(token: string, thresholdSeconds?: number): boolean;
|
|
@@ -6,6 +6,10 @@ export type * from "./types.js";
|
|
|
6
6
|
* Register a custom OAuth provider.
|
|
7
7
|
*/
|
|
8
8
|
export declare function registerOAuthProvider(provider: OAuthProviderInterface): void;
|
|
9
|
+
/**
|
|
10
|
+
* Remove a custom OAuth provider by ID.
|
|
11
|
+
*/
|
|
12
|
+
export declare function unregisterOAuthProvider(id: string): void;
|
|
9
13
|
/**
|
|
10
14
|
* Get a custom OAuth provider by ID.
|
|
11
15
|
*/
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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";
|
|
@@ -650,8 +649,10 @@ export interface DeveloperMessage {
|
|
|
650
649
|
providerPayload?: ProviderPayload;
|
|
651
650
|
timestamp: number;
|
|
652
651
|
}
|
|
652
|
+
/** How an automatic retry recovered or ultimately settled a failed attempt. */
|
|
653
653
|
export type AssistantRetryRecoveryKind = "credential" | "model" | "wait" | "plain";
|
|
654
|
-
|
|
654
|
+
/** Persisted presentation state for an assistant error superseded by an automatic retry saga. */
|
|
655
|
+
export type AssistantRetryRecovery = {
|
|
655
656
|
kind: "auto-retry";
|
|
656
657
|
status: "recovered";
|
|
657
658
|
attempt: number;
|
|
@@ -664,7 +665,13 @@ export interface AssistantRetryRecovery {
|
|
|
664
665
|
provider: string;
|
|
665
666
|
model: string;
|
|
666
667
|
};
|
|
667
|
-
}
|
|
668
|
+
} | {
|
|
669
|
+
kind: "auto-retry";
|
|
670
|
+
status: "superseded";
|
|
671
|
+
attempt: number;
|
|
672
|
+
recovery: AssistantRetryRecoveryKind;
|
|
673
|
+
note: string;
|
|
674
|
+
};
|
|
668
675
|
export interface ContextSnapshot {
|
|
669
676
|
promptTokens: number;
|
|
670
677
|
nonMessageTokens: number;
|
|
@@ -928,12 +935,12 @@ export type TJsonSchema = Record<string, unknown>;
|
|
|
928
935
|
/**
|
|
929
936
|
* Schema type accepted by the {@link Tool} interface.
|
|
930
937
|
*
|
|
931
|
-
* Canonical authoring uses
|
|
932
|
-
*
|
|
938
|
+
* Canonical authoring uses ArkType. Extension compat may supply a JSON Schema
|
|
939
|
+
* object (including TypeBox static schema objects).
|
|
933
940
|
*/
|
|
934
|
-
export type TSchema =
|
|
941
|
+
export type TSchema = Type | TJsonSchema;
|
|
935
942
|
/** Resolve parameter types for tool execution / handlers. */
|
|
936
|
-
export type Static<S> = S extends
|
|
943
|
+
export type Static<S> = S extends Type ? S["infer"] : S extends {
|
|
937
944
|
static: infer T;
|
|
938
945
|
} ? T : unknown;
|
|
939
946
|
export interface ToolCallExample<TArgs = Record<string, unknown>> {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { UsageProvider, UsageReport } from "../usage.js";
|
|
2
|
+
export declare function parseCursorIndividualUsage(payload: unknown, fetchedAt?: number): UsageReport | null;
|
|
2
3
|
export declare function parseCursorUsage(payload: unknown, fetchedAt?: number): UsageReport | null;
|
|
3
4
|
export declare const cursorUsageProvider: UsageProvider;
|
|
@@ -38,11 +38,10 @@ export declare function finalizeErrorMessage(error: unknown, rawRequestDump: Raw
|
|
|
38
38
|
* Rewrite error message for GitHub Copilot request failures.
|
|
39
39
|
* Must run AFTER finalizeErrorMessage since it replaces the message entirely.
|
|
40
40
|
*
|
|
41
|
-
* 400
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* in
|
|
45
|
-
* surface guidance rather than the raw error.
|
|
41
|
+
* 400 `model_not_supported` = Copilot fleet skew. A model that `/models`
|
|
42
|
+
* advertises can flap between 200 and 400 because only part of
|
|
43
|
+
* Copilot's fleet has it in the integrator allowlist. After the
|
|
44
|
+
* in-request retry exhausts, surface guidance rather than the raw error.
|
|
46
45
|
* 401 = token invalid/expired → credential removal is safe, prompt re-login.
|
|
47
46
|
* 403 = token valid but access denied (plan, model policy, org restriction) →
|
|
48
47
|
* do NOT reuse the auth-failed string (which triggers credential removal).
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { isCopilotTransientModelError } from "../error/flags.js";
|
|
2
2
|
export { isCopilotTransientModelError };
|
|
3
3
|
/**
|
|
4
|
-
* Wrap an initial Copilot request so transient
|
|
5
|
-
*
|
|
6
|
-
* small number of times. No-op for non-Copilot providers.
|
|
4
|
+
* Wrap an initial Copilot request so transient `model_not_supported` 400s are
|
|
5
|
+
* retried a small number of times. No-op for non-Copilot providers.
|
|
7
6
|
*
|
|
8
7
|
* The callback **MUST** create a fresh in-flight request each invocation — a
|
|
9
8
|
* once-consumed AsyncIterable cannot be re-iterated.
|
|
@@ -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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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 `{}` (
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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
|
|
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.
|
|
70
|
-
* JSON Schema parameters are upgraded to draft 2020-12
|
|
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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
36
|
-
*
|
|
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
|
*/
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "17.2.11",
|
|
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.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "17.2.
|
|
43
|
-
"@oh-my-pi/pi-utils": "17.2.
|
|
44
|
-
"@oh-my-pi/pi-wire": "17.2.
|
|
45
|
-
"zod": "^4"
|
|
41
|
+
"@oh-my-pi/omptype": "17.2.11",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "17.2.11",
|
|
43
|
+
"@oh-my-pi/pi-utils": "17.2.11",
|
|
44
|
+
"@oh-my-pi/pi-wire": "17.2.11"
|
|
46
45
|
},
|
|
47
46
|
"devDependencies": {
|
|
48
47
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
|
@@ -1374,11 +1374,13 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1374
1374
|
try {
|
|
1375
1375
|
let hasActiveApiKey = false;
|
|
1376
1376
|
const activeIdentityKeys = new Set<string>();
|
|
1377
|
+
const activeOAuthCredentials: AuthCredential[] = [];
|
|
1377
1378
|
for (const row of activeRows) {
|
|
1378
1379
|
if (row.credential.type === "api_key") {
|
|
1379
1380
|
hasActiveApiKey = true;
|
|
1380
1381
|
continue;
|
|
1381
1382
|
}
|
|
1383
|
+
activeOAuthCredentials.push(row.credential);
|
|
1382
1384
|
const identityKey = resolveCredentialIdentityKey(provider, row.credential);
|
|
1383
1385
|
if (identityKey) activeIdentityKeys.add(identityKey);
|
|
1384
1386
|
}
|
|
@@ -1393,7 +1395,22 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1393
1395
|
const identityKey = resolveRowCredentialIdentityKey(provider, row);
|
|
1394
1396
|
if (identityKey && activeIdentityKeys.has(identityKey)) {
|
|
1395
1397
|
this.#hardDeleteStmt.run(row.id);
|
|
1398
|
+
continue;
|
|
1396
1399
|
}
|
|
1400
|
+
// Exact key equality misses a tombstone whose key predates a format
|
|
1401
|
+
// the active row now uses (pre-org `<b>` vs `<b>|org:<o>`). An active
|
|
1402
|
+
// credential that WOULD have replaced this row had it still been
|
|
1403
|
+
// active supersedes its tombstone too, so mirror the replacement
|
|
1404
|
+
// matcher rather than restating a weaker rule. The one-way upgrade
|
|
1405
|
+
// and shared-workspace guards in matchesReplacementCredential carry
|
|
1406
|
+
// over, so this never over-deletes another member's or subscription's
|
|
1407
|
+
// row.
|
|
1408
|
+
const disabledCredential = deserializeCredential(row);
|
|
1409
|
+
if (disabledCredential === null) continue;
|
|
1410
|
+
const superseded = activeOAuthCredentials.some(active =>
|
|
1411
|
+
matchesReplacementCredential(provider, disabledCredential, identityKey, active),
|
|
1412
|
+
);
|
|
1413
|
+
if (superseded) this.#hardDeleteStmt.run(row.id);
|
|
1397
1414
|
}
|
|
1398
1415
|
} catch {
|
|
1399
1416
|
// Best-effort cleanup; don't let it break the main operation
|
package/src/error/flags.ts
CHANGED
|
@@ -113,17 +113,15 @@ const STALE_RESPONSE_ITEM_DETAIL_PATTERN = /not[ _]?found|invalid|expired|stale|
|
|
|
113
113
|
export const LLAMA_CPP_TOOL_CALL_PARSE_PATTERN =
|
|
114
114
|
/failed to parse tool call arguments as json|\[json\.exception\.parse_error\.101\]/i;
|
|
115
115
|
|
|
116
|
-
// Copilot fleet skew: HTTP 400
|
|
117
|
-
// the
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
116
|
+
// Copilot fleet skew: HTTP 400 `model_not_supported` can reject a model that
|
|
117
|
+
// `/models` advertised on the same host when the request lands on a stale
|
|
118
|
+
// replica. `model_not_available_for_integrator` is deliberately excluded:
|
|
119
|
+
// GitHub also uses it for stable per-integrator entitlement denials and includes
|
|
120
|
+
// that integrator's actionable `Available models` list in the response.
|
|
121
121
|
const COPILOT_TRANSIENT_MODEL_CODES: Record<string, true> = {
|
|
122
122
|
model_not_supported: true,
|
|
123
|
-
model_not_available_for_integrator: true,
|
|
124
123
|
};
|
|
125
|
-
const
|
|
126
|
-
/model_not_supported|model_not_available_for_integrator|not available for integrator/i;
|
|
124
|
+
const COPILOT_TRANSIENT_MODEL_PATTERN = /model_not_supported/i;
|
|
127
125
|
// Anthropic strict-tool grammar too large / schema too complex (400 invalid_request_error).
|
|
128
126
|
// Feature-gated deployments (Azure Foundry, Baseten, …) reject `strict: true`
|
|
129
127
|
// tools outright when the hosted model lacks structured outputs, e.g.
|
|
@@ -377,8 +375,8 @@ function classifyText(errorMessage: string | undefined, errorStatus: number | un
|
|
|
377
375
|
kinds |= Flag.StaleResponsesItem;
|
|
378
376
|
}
|
|
379
377
|
|
|
380
|
-
// Copilot fleet-skew
|
|
381
|
-
if (statusClean === 400 &&
|
|
378
|
+
// Copilot's `model_not_supported` fleet-skew rejection is transient.
|
|
379
|
+
if (statusClean === 400 && COPILOT_TRANSIENT_MODEL_PATTERN.test(cleanMessage)) kinds |= Flag.Transient;
|
|
382
380
|
if (matchesStrictToolsRejection(cleanMessage, statusClean)) kinds |= Flag.Grammar;
|
|
383
381
|
if (matchesFastModeUnsupported(cleanMessage, statusClean)) kinds |= Flag.FastModeUnsupported;
|
|
384
382
|
}
|
|
@@ -513,10 +511,10 @@ function providerErrorCode(error: object): string | undefined {
|
|
|
513
511
|
}
|
|
514
512
|
|
|
515
513
|
/**
|
|
516
|
-
* GitHub Copilot 400
|
|
517
|
-
* transient fleet skew, not a malformed request. Reads the
|
|
518
|
-
* through the SDK/body envelopes, then falls back to the
|
|
519
|
-
* SDK families put in `message
|
|
514
|
+
* GitHub Copilot 400 `model_not_supported` response for a model advertised by
|
|
515
|
+
* `/models` — transient fleet skew, not a malformed request. Reads the
|
|
516
|
+
* structural `code` through the SDK/body envelopes, then falls back to the
|
|
517
|
+
* stringified body both SDK families put in `message`.
|
|
520
518
|
*/
|
|
521
519
|
export function isCopilotTransientModelError(error: unknown): boolean {
|
|
522
520
|
if (!error || typeof error !== "object" || status(error) !== 400) return false;
|
|
@@ -525,7 +523,7 @@ export function isCopilotTransientModelError(error: unknown): boolean {
|
|
|
525
523
|
// prototype key (`__proto__`, `toString`, …) would otherwise read truthy.
|
|
526
524
|
if (code !== undefined && Object.hasOwn(COPILOT_TRANSIENT_MODEL_CODES, code)) return true;
|
|
527
525
|
const message: unknown = "message" in error ? error.message : undefined;
|
|
528
|
-
return typeof message === "string" &&
|
|
526
|
+
return typeof message === "string" && COPILOT_TRANSIENT_MODEL_PATTERN.test(message);
|
|
529
527
|
}
|
|
530
528
|
|
|
531
529
|
export function classifyMessage(message: {
|
package/src/error/rate-limit.ts
CHANGED
|
@@ -22,6 +22,13 @@ const ACCOUNT_RATE_LIMIT_PATTERN =
|
|
|
22
22
|
/\baccount(?:'s)?\b[^\n]{0,80}\brate.?limit\b|\brate.?limit\b[^\n]{0,80}\baccount\b/i;
|
|
23
23
|
const INSUFFICIENT_BALANCE_PATTERN = /insufficient.?balance/i;
|
|
24
24
|
const SPEND_LIMIT_PATTERN = /spend.?limit/i;
|
|
25
|
+
const SUBSCRIPTION_CAP_PATTERN =
|
|
26
|
+
/\b(?:subscription|plan|membership)\b[^\n]{0,80}\b(?:rate.?limits?|quota|cap)\b|\b(?:rate.?limits?|quota|cap)\b[^\n]{0,80}\b(?:subscription|plan|membership)\b/i;
|
|
27
|
+
const TRANSIENT_INTERVAL_RATE_LIMIT_PATTERN = /\bper\s+(?:second|minute)\b/i;
|
|
28
|
+
|
|
29
|
+
function matchesSubscriptionCapText(errorMessage: string): boolean {
|
|
30
|
+
return SUBSCRIPTION_CAP_PATTERN.test(errorMessage) && !TRANSIENT_INTERVAL_RATE_LIMIT_PATTERN.test(errorMessage);
|
|
31
|
+
}
|
|
25
32
|
const OPENROUTER_DAILY_FREE_LIMIT_PATTERN = /\bfree[-_ ]models[-_ ]per[-_ ]day\b/i;
|
|
26
33
|
// gRPC/Connect end-streams carry the status as its name (`resource_exhausted`),
|
|
27
34
|
// while HTTP bodies use the phrase ("resource exhausted"). Strip either form
|
|
@@ -40,6 +47,25 @@ const ACCOUNT_SCOPED_403_PATTERN =
|
|
|
40
47
|
// "Your limit will reset in …"); the overall/account qualifiers arm above
|
|
41
48
|
// already covers the rest.
|
|
42
49
|
/\b(?:overall|account|organization|team|workspace)\b[^\n]{0,40}\b(?:message |request )?rate.?limit\b|\byour\b[^\n]{0,30}\b(?:limit )?will reset\b/i;
|
|
50
|
+
// Simplified Chinese account-quota exhaustion phrasing. Zhipu Coding Plan
|
|
51
|
+
// returns e.g. "429 已达到 5 小时的使用上限。您的限额将在 2026-08-06 20:06:00 重置。"
|
|
52
|
+
// (type=1308) when the 5h window is spent; other CN providers use 额度已用完 /
|
|
53
|
+
// 配额已耗尽 / 余额不足. These are persistent account-local caps that must
|
|
54
|
+
// rotate to a sibling credential, not transient rate limits, so they are
|
|
55
|
+
// matched before the RATE_LIMIT_EXCEEDED branch. The 上限 arm is anchored on
|
|
56
|
+
// the 使用 token: a rate/concurrency cap phrased as 每分钟请求数已达上限 /
|
|
57
|
+
// 并发请求数已达上限 / 速率达到上限 (no 使用) must NOT match, or it would burn a
|
|
58
|
+
// healthy sibling credential as a false quota. "速率限制" is absent for the
|
|
59
|
+
// same reason.
|
|
60
|
+
const CN_QUOTA_EXHAUSTED_PATTERN = /使用.{0,30}?上限|(?:额度|配额)已?(?:用|耗)(?:完|尽)|限额.{0,30}重置|余额不足/;
|
|
61
|
+
// Simplified Chinese rate/concurrency caps can contain both 使用 and 上限, but
|
|
62
|
+
// remain transient rather than account quota exhaustion.
|
|
63
|
+
const CN_TRANSIENT_CAP_PATTERN =
|
|
64
|
+
/速率.{0,30}上限|频率.{0,30}上限|每分钟.{0,30}上限|并发.{0,30}上限|使用.{0,30}(?:速率|频率|每分钟|并发).{0,30}上限/;
|
|
65
|
+
// Common Simplified Chinese throttle phrasing. Consulted by
|
|
66
|
+
// isOpaqueStatusBody so CN transients stay in the provider backoff lane instead
|
|
67
|
+
// of rotating through the opaque-429 fallback.
|
|
68
|
+
const CN_THROTTLE_PATTERN = /速率(?:限制|过快)|频率(?:过高|过快)|过于频繁|稍后[重再]试/;
|
|
43
69
|
|
|
44
70
|
/**
|
|
45
71
|
* Classify a rate-limit error message into a reason category.
|
|
@@ -64,6 +90,13 @@ export function parseRateLimitReason(errorMessage: string): RateLimitReason {
|
|
|
64
90
|
return "QUOTA_EXHAUSTED";
|
|
65
91
|
}
|
|
66
92
|
|
|
93
|
+
// Simplified Chinese quota-exhaustion phrasing (Zhipu Coding Plan and other
|
|
94
|
+
// CN providers). Must precede the MODEL_CAPACITY / RATE_LIMIT branches so an
|
|
95
|
+
// account-local cap rotates instead of backing off as a transient.
|
|
96
|
+
if (CN_QUOTA_EXHAUSTED_PATTERN.test(errorMessage) && !CN_TRANSIENT_CAP_PATTERN.test(errorMessage)) {
|
|
97
|
+
return "QUOTA_EXHAUSTED";
|
|
98
|
+
}
|
|
99
|
+
|
|
67
100
|
if (CONCURRENT_LIMIT_PATTERN.test(errorMessage)) {
|
|
68
101
|
return "CONCURRENT_LIMIT";
|
|
69
102
|
}
|
|
@@ -80,6 +113,10 @@ export function parseRateLimitReason(errorMessage: string): RateLimitReason {
|
|
|
80
113
|
return "QUOTA_EXHAUSTED";
|
|
81
114
|
}
|
|
82
115
|
|
|
116
|
+
if (matchesSubscriptionCapText(errorMessage)) {
|
|
117
|
+
return "QUOTA_EXHAUSTED";
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
if (OPENROUTER_DAILY_FREE_LIMIT_PATTERN.test(errorMessage)) {
|
|
84
121
|
return "QUOTA_EXHAUSTED";
|
|
85
122
|
}
|
|
@@ -212,7 +249,20 @@ export function isOpaqueStatusBody(message: string): boolean {
|
|
|
212
249
|
const cleaned = message
|
|
213
250
|
.replace(/\b(?:429|402)\b/g, "")
|
|
214
251
|
.replace(/\b(?:http|https|status|error|code|response|message)\b/gi, "");
|
|
215
|
-
|
|
252
|
+
// A body is informative when the text classifier can act on it. Any Latin
|
|
253
|
+
// word or Simplified Chinese phrasing the classifier recognizes (quota
|
|
254
|
+
// exhaustion or a throttle) defers to parseRateLimitReason; a body that
|
|
255
|
+
// is only status digits / HTTP framing is opaque and rotates conservatively.
|
|
256
|
+
// A Han-only body the classifier cannot interpret (e.g. Japanese Kanji
|
|
257
|
+
// quota text, since Japanese is out of scope) must stay opaque so the
|
|
258
|
+
// opaque-429 fallback still rotates. This keeps the exception scoped to
|
|
259
|
+
// text we actually classify, rather than to any Han ideograph.
|
|
260
|
+
return (
|
|
261
|
+
!/[a-z\d]{3,}/i.test(cleaned) &&
|
|
262
|
+
!CN_QUOTA_EXHAUSTED_PATTERN.test(cleaned) &&
|
|
263
|
+
!CN_TRANSIENT_CAP_PATTERN.test(cleaned) &&
|
|
264
|
+
!CN_THROTTLE_PATTERN.test(cleaned)
|
|
265
|
+
);
|
|
216
266
|
}
|
|
217
267
|
|
|
218
268
|
/**
|
|
@@ -224,8 +274,10 @@ export function isOpaqueStatusBody(message: string): boolean {
|
|
|
224
274
|
export function matchesUsageLimitText(errorMessage: string): boolean {
|
|
225
275
|
return (
|
|
226
276
|
USAGE_LIMIT_PATTERN.test(errorMessage) ||
|
|
277
|
+
(CN_QUOTA_EXHAUSTED_PATTERN.test(errorMessage) && !CN_TRANSIENT_CAP_PATTERN.test(errorMessage)) ||
|
|
227
278
|
SPEND_LIMIT_PATTERN.test(errorMessage) ||
|
|
228
279
|
ACCOUNT_RATE_LIMIT_PATTERN.test(errorMessage) ||
|
|
280
|
+
matchesSubscriptionCapText(errorMessage) ||
|
|
229
281
|
OPENROUTER_DAILY_FREE_LIMIT_PATTERN.test(errorMessage)
|
|
230
282
|
);
|
|
231
283
|
}
|
package/src/index.ts
CHANGED