@oh-my-pi/pi-ai 18.14.1 → 18.15.0
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 +37 -60
- package/dist/types/auth/refresh.d.ts +13 -0
- package/dist/types/auth/types.d.ts +11 -5
- package/dist/types/auth-storage.d.ts +9 -4
- package/dist/types/providers/anthropic.d.ts +5 -0
- package/dist/types/providers/factory-droid.d.ts +2 -0
- package/dist/types/providers/google-auth.d.ts +2 -3
- package/dist/types/providers/openai-chat-server.d.ts +7 -1
- package/dist/types/providers/openai-codex/request-transformer.d.ts +1 -0
- package/dist/types/providers/openai-completions.d.ts +13 -0
- package/dist/types/providers/openai-responses-server-schema.d.ts +12 -12
- package/dist/types/types.d.ts +6 -2
- package/dist/types/utils/retry-after.d.ts +3 -1
- package/dist/types/utils/thinking-loop.d.ts +14 -9
- package/dist/types/utils.d.ts +5 -0
- package/package.json +6 -6
- package/src/auth/refresh.ts +49 -11
- package/src/auth/types.ts +11 -5
- package/src/auth/usage.ts +1 -0
- package/src/auth-broker/client.ts +1 -1
- package/src/auth-broker/server.ts +7 -2
- package/src/auth-gateway/server.ts +1 -0
- package/src/auth-storage.ts +18 -6
- package/src/providers/anthropic.ts +42 -4
- package/src/providers/factory-droid.ts +65 -1
- package/src/providers/google-auth.ts +57 -13
- package/src/providers/openai-chat-server.ts +69 -9
- package/src/providers/openai-codex/request-transformer.ts +8 -8
- package/src/providers/openai-completions.ts +25 -0
- package/src/providers/openai-responses-server-schema.ts +5 -5
- package/src/providers/openai-responses-server.ts +3 -3
- package/src/providers/openai-shared.ts +6 -5
- package/src/stream.ts +1 -0
- package/src/types.ts +11 -3
- package/src/usage/cursor.ts +7 -1
- package/src/utils/http-inspector.ts +47 -14
- package/src/utils/retry-after.ts +10 -3
- package/src/utils/thinking-loop.ts +38 -27
- package/src/utils.ts +15 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.15.0] - 2026-11-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Codex conversation replay failing with an unknown `internal_chat_message_metadata_passthrough.content_token_count` parameter by omitting backend-internal response metadata from replayed input ([#17590](https://github.com/can1357/oh-my-pi/pull/17590), [#17597](https://github.com/can1357/oh-my-pi/pull/17597) by [@briankeefe](https://github.com/briankeefe)).
|
|
10
|
+
- Fixed `service_tier: "ultrafast"` being dropped for OpenAI-family models on custom providers even when the model's `serviceTiers` lists it ([#17577](https://github.com/can1357/oh-my-pi/pull/17577) by [@jakehemmerle](https://github.com/jakehemmerle))
|
|
11
|
+
- Fixed Google Vertex requests with Workforce Identity Federation ADC failing token refresh against the wrong endpoint ([#17447](https://github.com/can1357/oh-my-pi/issues/17447)).
|
|
12
|
+
- Fixed Anthropic (and other rotating-token) OAuth accounts being signed out automatically when a background usage poll's token refresh got `invalid_grant`, including refreshes a client delegates to an auth broker; usage polling no longer disables credentials, so only a real request's refresh can do that ([#17468](https://github.com/can1357/oh-my-pi/issues/17468))
|
|
13
|
+
|
|
14
|
+
## [18.14.2] - 2026-10-31
|
|
15
|
+
|
|
16
|
+
### Breaking Changes
|
|
17
|
+
|
|
18
|
+
- Replaced `isLoopGuardedModel` with `loopGuardLevel`, which returns `"off"`, `"exact"`, or `"semantic"` from the model's `thinking-loop-guard` catalog rule
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- Added `disableServerSideFallback` to `FactoryDroidOptions` and `SimpleStreamOptions`: a Factory Droid request leaves out the server-side refusal fallback chain and fails, before any of its content is streamed, if another model answers it ([#12229](https://github.com/can1357/oh-my-pi/pull/12229) by [@Xytronix](https://github.com/Xytronix))
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- DeepSeek and Grok models on Amazon Bedrock now get the same near-duplicate reasoning-loop checks as on other hosts
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Fixed Claude turns failing with "Thinking loop detected" when summarized thinking repeated a progress line such as "Still writing the parser..."; Claude models no longer run the loop guard
|
|
31
|
+
- Fixed the loop guard discarding turns from other models whose reasoning summary repeats one status line a few times; whole-line repetition now has to cover about 1 KB before it counts as a loop
|
|
32
|
+
- Fixed per-request OpenAI-compatible headers being combined with differently cased model headers instead of overriding them ([#17513](https://github.com/can1357/oh-my-pi/pull/17513) by [@jchanghong023](https://github.com/jchanghong023))
|
|
33
|
+
- Fixed a stalled Anthropic stream that keeps sending empty content deltas never timing out, because those deltas were treated as stream progress ([#17484](https://github.com/can1357/oh-my-pi/pull/17484) by [@jchanghong023](https://github.com/jchanghong023))
|
|
34
|
+
- Fixed Anthropic-compatible endpoints that put a thinking signature on the streamed content block losing it, so replayed reasoning keeps its signed block ([#17483](https://github.com/can1357/oh-my-pi/pull/17483) by [@jchanghong023](https://github.com/jchanghong023))
|
|
35
|
+
- Fixed `AuthStorage.close()` and `replaceStore()` losing a rotated OAuth refresh token when a refresh was still in flight, with or without durable refresh leases; the store now stays open until that token is saved, and new refreshes are refused ([#17518](https://github.com/can1357/oh-my-pi/issues/17518))
|
|
36
|
+
- Fixed `/usage` showing Cursor's "Other Models" meter as an inflated dollar amount; it now shows the percentage Cursor reports, like Cursor Models ([#17462](https://github.com/can1357/oh-my-pi/pull/17462) by [@eggpeat](https://github.com/eggpeat))
|
|
37
|
+
- Fixed the `omp auth-gateway` Responses endpoint rejecting documented null values for temperature, `top_p`, output limits, reasoning, and reasoning summaries ([#17466](https://github.com/can1357/oh-my-pi/pull/17466) by [@Xytronix](https://github.com/Xytronix)).
|
|
38
|
+
- Fixed provider HTTP errors printing the response body twice when the thrown error already carried it, including responses with a retry header ([#17503](https://github.com/can1357/oh-my-pi/pull/17503) by [@metaphoric-dev](https://github.com/metaphoric-dev))
|
|
39
|
+
- Fixed Gemini tool calls through the `omp auth-gateway` chat-completions endpoint failing on the next turn: the gateway now returns each call's thought signature in `extra_content` and replays the one a client sends back ([#17509](https://github.com/can1357/oh-my-pi/pull/17509) by [@justdoGIT](https://github.com/justdoGIT))
|
|
40
|
+
|
|
5
41
|
## [18.14.1] - 2026-10-31
|
|
6
42
|
|
|
7
43
|
### Added
|
|
@@ -2260,63 +2296,4 @@
|
|
|
2260
2296
|
- Fixed an issue where malformed tool-call JSON from local Ollama or llama.cpp models was incorrectly retried as generic 500 errors, now surfacing a clear recovery message.
|
|
2261
2297
|
- Fixed a race condition in OAuth callback flows where abort signals triggered before the callback listener was registered were ignored.
|
|
2262
2298
|
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
### Added
|
|
2266
|
-
|
|
2267
|
-
- Added service tier support for Google Gemini and Vertex AI, including model-specific service tier configurations via ServiceTierByFamily.
|
|
2268
|
-
- Added Google Vertex AI Interactions API support for Gemini 3+ models by default, with automatic fallback to :streamGenerateContent and a useInteractionsApi: false option to force standard generation.
|
|
2269
|
-
- Added support for explicit Vertex bearer access tokens via GOOGLE_CLOUD_ACCESS_TOKEN or CLOUDSDK_AUTH_ACCESS_TOKEN environment variables.
|
|
2270
|
-
|
|
2271
|
-
### Changed
|
|
2272
|
-
|
|
2273
|
-
- Updated service tier logic to use per-provider configurations instead of global scopes.
|
|
2274
|
-
- Refactored priority request billing and accounting to better align with specific provider capabilities.
|
|
2275
|
-
- Updated API key resolution precedence so explicit environment variables (e.g., GEMINI_API_KEY) override stored or broker-migrated static API keys, while deliberate OAuth logins still take highest precedence.
|
|
2276
|
-
|
|
2277
|
-
### Fixed
|
|
2278
|
-
|
|
2279
|
-
- Improved Vertex AI reliability by automatically falling back to global endpoints on 404 errors.
|
|
2280
|
-
- Fixed safety setting application for Google Vertex AI models.
|
|
2281
|
-
- Fixed Kimi Code's Anthropic-compatible request path to keep thinking enabled and downgrade forced tool choice for Kimi K2.7 Code title generation.
|
|
2282
|
-
- Fixed leaked reasoning fences (such as ```thinking or <think>) across all providers by splitting them into structured thinking blocks during streaming.
|
|
2283
|
-
- Fixed Codex requests failing with unsupported all_turns errors on older models (gpt-5.1 and gpt-5.3) by gating the reasoning.context: "all_turns" default to gpt-5.4+ models.
|
|
2284
|
-
|
|
2285
|
-
## [16.2.6] - 2026-06-29
|
|
2286
|
-
|
|
2287
|
-
### Fixed
|
|
2288
|
-
|
|
2289
|
-
- Fixed Antigravity usage reporting to correctly infer daily and weekly quota windows from unlabeled reset-only rows, preventing Cloud Code Assist payloads from collapsing these counters into the default category.
|
|
2290
|
-
|
|
2291
|
-
## [16.2.5] - 2026-06-28
|
|
2292
|
-
|
|
2293
|
-
### Fixed
|
|
2294
|
-
|
|
2295
|
-
- Fixed Google and Cloud Code Assist streams that end without a finish reason (dropped connections or truncated responses) being treated as fatal; they are now classified as transient so the coding agent automatically retries.
|
|
2296
|
-
|
|
2297
|
-
## [16.2.4] - 2026-06-28
|
|
2298
|
-
|
|
2299
|
-
### Added
|
|
2300
|
-
|
|
2301
|
-
- Enabled freeform tool patch support for Azure OpenAI and Codex models
|
|
2302
|
-
|
|
2303
|
-
### Fixed
|
|
2304
|
-
|
|
2305
|
-
- Fixed usage reporting for Antigravity and Z.AI to correctly surface and preserve distinct quota windows (daily, weekly, monthly) instead of collapsing or duplicating them
|
|
2306
|
-
- Fixed an issue where `/usage show` returned "No usage data available" when using a custom proxy base URL for Codex
|
|
2307
|
-
- Fixed OpenAI stream read errors being incorrectly classified as non-transient, enabling the coding agent to automatically retry after recoverable stream failures
|
|
2308
|
-
|
|
2309
|
-
## [16.2.3] - 2026-06-28
|
|
2310
|
-
|
|
2311
|
-
### Changed
|
|
2312
|
-
|
|
2313
|
-
- Enabled automatic removal of leaked reasoning tags for all models
|
|
2314
|
-
- Prevented reasoning text duplication when models emit both structured and inline thinking
|
|
2315
|
-
- Defaulted reasoning context to all turns for all Codex requests.
|
|
2316
|
-
|
|
2317
|
-
### Fixed
|
|
2318
|
-
|
|
2319
|
-
- Enabled freeform tool patch support for Azure OpenAI and Codex models.
|
|
2320
|
-
- Fixed an issue where the `/usage show` command returned "No usage data available" when using a custom proxy base URL for Codex.
|
|
2321
|
-
|
|
2322
|
-
Older entries are archived in [packages/ai/CHANGELOG.md@cef4a452836c](https://github.com/can1357/oh-my-pi/blob/cef4a452836c7a8d547dd14470c34c9ed2dbb5f8/packages/ai/CHANGELOG.md).
|
|
2299
|
+
Older entries are archived in [packages/ai/CHANGELOG.md@7dc0d0325754](https://github.com/can1357/oh-my-pi/blob/7dc0d0325754955b01c15b64b220b2cf27e670db/packages/ai/CHANGELOG.md).
|
|
@@ -40,6 +40,14 @@ export declare class OAuthRefresher {
|
|
|
40
40
|
* Refresh one stored OAuth credential under durable row ownership.
|
|
41
41
|
*/
|
|
42
42
|
refreshStored<T extends OAuthCredential = OAuthCredential>(provider: string, options: StoredOAuthRefreshOptions<T>): Promise<StoredOAuthRefreshResult<T>>;
|
|
43
|
+
/**
|
|
44
|
+
* Stop admitting refreshes, then run `closeStore` once tracked work settles:
|
|
45
|
+
* a token exchange already under way, its save, a lease hand-back. Runs it at
|
|
46
|
+
* once when nothing is in flight. Closing the store under that work would
|
|
47
|
+
* lose a refresh token the provider already rotated. Called by
|
|
48
|
+
* {@link AuthStorage.close} and {@link AuthStorage.replaceStore}.
|
|
49
|
+
*/
|
|
50
|
+
close(closeStore: () => void): void;
|
|
43
51
|
/**
|
|
44
52
|
* Handles a definitively-dead OAuth grant discovered during refresh (`invalid_grant`,
|
|
45
53
|
* `revoked`, …): checks for a peer rotation that raced the failure, then CAS-disables
|
|
@@ -54,6 +62,11 @@ export declare class OAuthRefresher {
|
|
|
54
62
|
* continuing.
|
|
55
63
|
*/
|
|
56
64
|
disableDefinitiveFailure(provider: string, credentialId: number | undefined, attemptedCredential: OAuthCredential, index: number, errorMsg: string): Promise<"disabled" | "peer-rotated" | "cas-lost">;
|
|
65
|
+
/**
|
|
66
|
+
* Refresh `credential` unless it is still fresh. Shutdown waits for the
|
|
67
|
+
* refresh, and for the caller's synchronous save of its result, before the
|
|
68
|
+
* store closes.
|
|
69
|
+
*/
|
|
57
70
|
refresh(provider: Provider, credential: OAuthCredential, credentialId: number | undefined, signal?: AbortSignal, reason?: OAuthRefreshReason, rejectedAccess?: string): Promise<OAuthCredentials>;
|
|
58
71
|
/**
|
|
59
72
|
* Refresh the OAuth credential with the given id through a per-credential
|
|
@@ -279,8 +279,13 @@ export interface CredentialDisabledEvent {
|
|
|
279
279
|
orgId?: string;
|
|
280
280
|
orgName?: string;
|
|
281
281
|
}
|
|
282
|
-
/**
|
|
283
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Why an OAuth refresh was requested.
|
|
284
|
+
* - `auth-recovery`: a provider rejected the bearer with a 401.
|
|
285
|
+
* - `usage-probe`: a background usage poll; advisory, so a definitive refresh
|
|
286
|
+
* failure never disables the credential (an interactive request decides).
|
|
287
|
+
*/
|
|
288
|
+
export type OAuthRefreshReason = "auth-recovery" | "usage-probe";
|
|
284
289
|
/** Configuration supplied when constructing credential storage. */
|
|
285
290
|
export type AuthStorageOptions = {
|
|
286
291
|
usageProviderResolver?: (provider: Provider) => UsageProvider | undefined;
|
|
@@ -979,9 +984,10 @@ export interface OAuthRefreshByIdOptions {
|
|
|
979
984
|
*/
|
|
980
985
|
reuseRecentMint?: boolean;
|
|
981
986
|
/**
|
|
982
|
-
*
|
|
983
|
-
* broker), which may then return a token it minted in the
|
|
984
|
-
*
|
|
987
|
+
* Why the refresh was requested. `auth-recovery` is forwarded to a delegated
|
|
988
|
+
* refresh (an auth broker), which may then return a token it minted in the
|
|
989
|
+
* last five minutes; local reuse still requires {@link reuseRecentMint}.
|
|
990
|
+
* `usage-probe` never disables the row on a definitive refresh failure.
|
|
985
991
|
*/
|
|
986
992
|
reason?: OAuthRefreshReason;
|
|
987
993
|
/** The access token a provider 401 rejected; see {@link AuthApiKeyOptions.rejectedAccessToken}. */
|
|
@@ -46,9 +46,10 @@ export declare class AuthStorage {
|
|
|
46
46
|
/**
|
|
47
47
|
* Swap the backing credential store in place (live `auth.broker.url` change).
|
|
48
48
|
* Loads `store` into fresh store-bound state — pins, blocks, and usage caches are
|
|
49
|
-
* keyed by the old store's row ids — then
|
|
50
|
-
* overrides, account policies, session
|
|
51
|
-
* overrides, and credential event
|
|
49
|
+
* keyed by the old store's row ids — then retires the previous store like
|
|
50
|
+
* {@link AuthStorage.close}. Runtime key overrides, account policies, session
|
|
51
|
+
* account restrictions, usage-provider overrides, and credential event
|
|
52
|
+
* subscribers carry over. On a load failure
|
|
52
53
|
* `store` is closed and the current store stays active.
|
|
53
54
|
*/
|
|
54
55
|
replaceStore(store: AuthCredentialStore, options?: {
|
|
@@ -56,7 +57,11 @@ export declare class AuthStorage {
|
|
|
56
57
|
}): Promise<void>;
|
|
57
58
|
/** Open the SQLite store at `dbPath` and wrap it (standalone use, e.g. the pi-ai CLI). */
|
|
58
59
|
static create(dbPath: string, options?: AuthStorageOptions): Promise<AuthStorage>;
|
|
59
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Close the underlying credential store; the instance must not be reused.
|
|
62
|
+
* New OAuth refreshes are refused at once; a token exchange already under way
|
|
63
|
+
* keeps the store open until it has saved the rotated token.
|
|
64
|
+
*/
|
|
60
65
|
close(): void;
|
|
61
66
|
/**
|
|
62
67
|
* Legacy redirect for callers of the pre-namespace flat API (e.g. repo scripts).
|
|
@@ -17,6 +17,11 @@ export * from "./claude-code-fingerprint.js";
|
|
|
17
17
|
* pass through untouched, so installing it on every OAuth flow is safe.
|
|
18
18
|
*/
|
|
19
19
|
export declare function wrapFetchForCch(base: FetchImpl): FetchImpl;
|
|
20
|
+
/**
|
|
21
|
+
* Whether a request to `model` sends the tool `name` for the host to run itself, never dispatching the call to the
|
|
22
|
+
* client: the same decision `encodeAnthropicToolName` makes on the wire.
|
|
23
|
+
*/
|
|
24
|
+
export declare function anthropicHostRunsTool(model: Model<"anthropic-messages">, name: string, headers?: Record<string, string>): boolean;
|
|
20
25
|
export type AnthropicEffort = AnthropicOutputEffort | "adaptive";
|
|
21
26
|
export type AnthropicThinkingDisplay = "summarized" | "omitted";
|
|
22
27
|
export interface AnthropicOptions extends StreamOptions {
|
|
@@ -3,6 +3,8 @@ import { type FactoryDroidWire } from "@oh-my-pi/pi-catalog/wire/factory-droid";
|
|
|
3
3
|
import type { OAuthRequestIdentity } from "../auth/types.js";
|
|
4
4
|
import type { Api, Model, ServiceTier, StreamFunction, StreamOptions, ToolChoice } from "../types.js";
|
|
5
5
|
export interface FactoryDroidOptions extends StreamOptions {
|
|
6
|
+
/** Per-call workers only: send no refusal fallbacks or fallback-credit beta, and fail a response served by another model. */
|
|
7
|
+
disableServerSideFallback?: boolean;
|
|
6
8
|
reasoning?: Effort;
|
|
7
9
|
disableReasoning?: boolean;
|
|
8
10
|
toolChoice?: ToolChoice;
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Replaces `google-auth-library` with a direct WebCrypto + REST implementation.
|
|
5
5
|
* Sources, in priority order:
|
|
6
|
-
* 1. `GOOGLE_APPLICATION_CREDENTIALS` env →
|
|
7
|
-
* or `type: "authorized_user"` (refresh-token exchange).
|
|
6
|
+
* 1. `GOOGLE_APPLICATION_CREDENTIALS` env → service account, user, or workforce ADC file.
|
|
8
7
|
* 2. gcloud user ADC: `%APPDATA%\gcloud\application_default_credentials.json` on Windows,
|
|
9
|
-
* `~/.config/gcloud/application_default_credentials.json` elsewhere
|
|
8
|
+
* `~/.config/gcloud/application_default_credentials.json` elsewhere.
|
|
10
9
|
* 3. GCE / Cloud Run metadata server (`metadata.google.internal`).
|
|
11
10
|
*
|
|
12
11
|
* Tokens are cached per source key and refreshed `GOOGLE_VERTEX_REFRESH_SKEW_MS` before expiry
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
* `stream(model, context, options)`.
|
|
4
4
|
*/
|
|
5
5
|
import type { AuthGatewayStreamControl, AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types.js";
|
|
6
|
-
import type { AssistantMessage, AssistantMessageEventStream } from "../types.js";
|
|
6
|
+
import type { AssistantMessage, AssistantMessageEventStream, Context, Model } from "../types.js";
|
|
7
7
|
export type { ParsedRequest };
|
|
8
8
|
export declare function parseRequest(body: unknown, headers?: Headers): ParsedRequest;
|
|
9
|
+
/**
|
|
10
|
+
* Stamp the replayed tool-call turns that carry a Gemini signature as `model`'s own: they came from this gateway
|
|
11
|
+
* model, and a turn labeled as a foreign model loses its signature in `transformMessages`. Native Gemini
|
|
12
|
+
* transports replay the raw signature; every other transport keeps the stored envelope.
|
|
13
|
+
*/
|
|
14
|
+
export declare function adoptReplayedGeminiSignatures(context: Context, model: Model): void;
|
|
9
15
|
export declare function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown>;
|
|
10
16
|
export declare function encodeStream(events: AssistantMessageEventStream, requestedModelId: string, options?: ParsedRequest["options"], control?: AuthGatewayStreamControl): ReadableStream<Uint8Array>;
|
|
11
17
|
/**
|
|
@@ -3,6 +3,19 @@ import type { AssistantMessage, Context, Model, ServiceTier, StreamFunction, Str
|
|
|
3
3
|
import type { ChatCompletionMessageParam } from "./openai-chat-wire.js";
|
|
4
4
|
import { type OpenAIPromptCacheOptions } from "./openai-shared.js";
|
|
5
5
|
export { applyOpenRouterRoutingVariant } from "./openai-shared.js";
|
|
6
|
+
type GeminiThoughtSignatureNamespace = "google" | "vertex";
|
|
7
|
+
type GeminiThoughtSignatureExtraContent = Partial<Record<GeminiThoughtSignatureNamespace, {
|
|
8
|
+
thought_signature: string;
|
|
9
|
+
}>>;
|
|
10
|
+
/** The stored signature envelope for a client-replayed `extra_content` (`google` or `vertex`), as this provider writes it. */
|
|
11
|
+
export declare function storeGeminiToolCallSignature(extraContent: unknown): string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* The `extra_content` that replays a tool call's Gemini signature on an OpenAI-compatible wire: read from the
|
|
14
|
+
* envelope this provider stores, or wrapped from the raw signature a native Gemini transport recorded.
|
|
15
|
+
*/
|
|
16
|
+
export declare function geminiToolCallExtraContent(thoughtSignature: string | undefined): GeminiThoughtSignatureExtraContent | undefined;
|
|
17
|
+
/** The raw Gemini signature behind a tool call's `thoughtSignature`, as native Gemini parts carry it. */
|
|
18
|
+
export declare function rawGeminiToolCallSignature(thoughtSignature: string | undefined): string | undefined;
|
|
6
19
|
/**
|
|
7
20
|
* Whether a thinking block's signature is a raw LiteLLM (Anthropic) one, not a
|
|
8
21
|
* reasoning field name or the Mistral content-parts marker.
|
|
@@ -853,10 +853,10 @@ export declare const toolChoiceSchema: import("@oh-my-pi/omptype").FluentType<"a
|
|
|
853
853
|
}>;
|
|
854
854
|
export declare const reasoningConfigSchema: import("@oh-my-pi/omptype").FluentType<{
|
|
855
855
|
effort?: string | undefined;
|
|
856
|
-
summary?: "auto" | "concise" | "detailed" | "none" | undefined;
|
|
856
|
+
summary?: "auto" | "concise" | "detailed" | "none" | null | undefined;
|
|
857
857
|
}, {
|
|
858
858
|
effort?: string | undefined;
|
|
859
|
-
summary?: "auto" | "concise" | "detailed" | "none" | undefined;
|
|
859
|
+
summary?: "auto" | "concise" | "detailed" | "none" | null | undefined;
|
|
860
860
|
}>;
|
|
861
861
|
export declare const stopSchema: import("@oh-my-pi/omptype").FluentType<string | string[] | null, string | string[] | null>;
|
|
862
862
|
export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").FluentType<{
|
|
@@ -1101,7 +1101,7 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1101
1101
|
type: string;
|
|
1102
1102
|
})[] | undefined;
|
|
1103
1103
|
instructions?: string | null | undefined;
|
|
1104
|
-
max_output_tokens?: number | undefined;
|
|
1104
|
+
max_output_tokens?: number | null | undefined;
|
|
1105
1105
|
metadata?: unknown;
|
|
1106
1106
|
model: string;
|
|
1107
1107
|
parallel_tool_calls?: boolean | undefined;
|
|
@@ -1111,14 +1111,14 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1111
1111
|
prompt_cache_key?: string | undefined;
|
|
1112
1112
|
reasoning?: {
|
|
1113
1113
|
effort?: string | undefined;
|
|
1114
|
-
summary?: "auto" | "concise" | "detailed" | "none" | undefined;
|
|
1115
|
-
} | undefined;
|
|
1114
|
+
summary?: "auto" | "concise" | "detailed" | "none" | null | undefined;
|
|
1115
|
+
} | null | undefined;
|
|
1116
1116
|
safety_identifier?: unknown;
|
|
1117
1117
|
service_tier?: string | undefined;
|
|
1118
1118
|
stop?: string | string[] | null | undefined;
|
|
1119
1119
|
store?: boolean | undefined;
|
|
1120
1120
|
stream?: boolean | undefined;
|
|
1121
|
-
temperature?: number | undefined;
|
|
1121
|
+
temperature?: number | null | undefined;
|
|
1122
1122
|
text?: unknown;
|
|
1123
1123
|
tool_choice?: "auto" | "none" | "required" | {
|
|
1124
1124
|
name: string;
|
|
@@ -1148,7 +1148,7 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1148
1148
|
type: string;
|
|
1149
1149
|
})[] | undefined;
|
|
1150
1150
|
top_logprobs?: unknown;
|
|
1151
|
-
top_p?: number | undefined;
|
|
1151
|
+
top_p?: number | null | undefined;
|
|
1152
1152
|
truncation?: unknown;
|
|
1153
1153
|
user?: string | undefined;
|
|
1154
1154
|
}, {
|
|
@@ -1393,7 +1393,7 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1393
1393
|
type: string;
|
|
1394
1394
|
})[] | undefined;
|
|
1395
1395
|
instructions?: string | null | undefined;
|
|
1396
|
-
max_output_tokens?: number | undefined;
|
|
1396
|
+
max_output_tokens?: number | null | undefined;
|
|
1397
1397
|
metadata?: unknown;
|
|
1398
1398
|
model: string;
|
|
1399
1399
|
parallel_tool_calls?: boolean | undefined;
|
|
@@ -1403,14 +1403,14 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1403
1403
|
prompt_cache_key?: string | undefined;
|
|
1404
1404
|
reasoning?: {
|
|
1405
1405
|
effort?: string | undefined;
|
|
1406
|
-
summary?: "auto" | "concise" | "detailed" | "none" | undefined;
|
|
1407
|
-
} | undefined;
|
|
1406
|
+
summary?: "auto" | "concise" | "detailed" | "none" | null | undefined;
|
|
1407
|
+
} | null | undefined;
|
|
1408
1408
|
safety_identifier?: unknown;
|
|
1409
1409
|
service_tier?: string | undefined;
|
|
1410
1410
|
stop?: string | string[] | null | undefined;
|
|
1411
1411
|
store?: boolean | undefined;
|
|
1412
1412
|
stream?: boolean | undefined;
|
|
1413
|
-
temperature?: number | undefined;
|
|
1413
|
+
temperature?: number | null | undefined;
|
|
1414
1414
|
text?: unknown;
|
|
1415
1415
|
tool_choice?: "auto" | "none" | "required" | {
|
|
1416
1416
|
name: string;
|
|
@@ -1440,7 +1440,7 @@ export declare const openaiResponsesRequestSchema: import("@oh-my-pi/omptype").F
|
|
|
1440
1440
|
type: string;
|
|
1441
1441
|
})[] | undefined;
|
|
1442
1442
|
top_logprobs?: unknown;
|
|
1443
|
-
top_p?: number | undefined;
|
|
1443
|
+
top_p?: number | null | undefined;
|
|
1444
1444
|
truncation?: unknown;
|
|
1445
1445
|
user?: string | undefined;
|
|
1446
1446
|
}>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -144,8 +144,10 @@ export declare function resolveModelServiceTier(tiers: ServiceTierByFamily | nul
|
|
|
144
144
|
* `service_tier_for_request`); an empty or missing list counts as "not
|
|
145
145
|
* reported" — accounts whose `/models` lists no tiers keep `/fast` — so the
|
|
146
146
|
* provider-level answer stands. `flex` and `default` are never gated.
|
|
147
|
-
* First-party OpenAI takes `ultrafast` as-is.
|
|
148
|
-
*
|
|
147
|
+
* First-party OpenAI takes `ultrafast` as-is. An OpenAI-family relay on the
|
|
148
|
+
* Responses/Completions APIs gets `ultrafast` only when its discovered
|
|
149
|
+
* `service_tiers` lists it. A bare provider string cannot carry the list, so
|
|
150
|
+
* it answers for the provider alone.
|
|
149
151
|
*/
|
|
150
152
|
export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, target: Provider | ServiceTierModel | undefined): boolean;
|
|
151
153
|
/**
|
|
@@ -631,6 +633,8 @@ export interface SimpleStreamOptions extends Omit<StreamOptions, "apiKey"> {
|
|
|
631
633
|
* provider. Non-Anthropic providers ignore the field.
|
|
632
634
|
*/
|
|
633
635
|
fallbacks?: FallbackParam[];
|
|
636
|
+
/** Factory Droid only: omit refusal fallbacks and reject responses served by another model. */
|
|
637
|
+
disableServerSideFallback?: boolean;
|
|
634
638
|
}
|
|
635
639
|
export type StreamFunction<TApi extends Api> = (model: Model<TApi>, context: Context, options: OptionsForApi<TApi>) => AssistantMessageEventStream;
|
|
636
640
|
export interface TextSignatureV1 {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type HeadersLike = Headers | Record<string, string | undefined> | undefined | null;
|
|
2
2
|
/** Extracts retry timing using the provider's catalog-declared timestamp timezone. */
|
|
3
3
|
export declare function extractProviderRetryHint(provider: string | undefined, message: string | undefined): number | undefined;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function errorMessageText(error: unknown): string;
|
|
5
|
+
export declare function appendRetryAfterHint(message: string, headers: HeadersLike): string;
|
|
6
|
+
export declare function formatErrorMessageWithRetryAfter(error: unknown): string;
|
|
5
7
|
export declare function getRetryAfterMsFromHeaders(headers: HeadersLike): number | undefined;
|
|
6
8
|
export declare function getHeadersFromError(error: unknown): HeadersLike;
|
|
@@ -5,11 +5,17 @@ import { AssistantMessageEventStream } from "./event-stream.js";
|
|
|
5
5
|
* classifiers treat it as a transient (retryable) stop without bespoke rules. */
|
|
6
6
|
export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* How much of the loop guard runs on a stream: `off` none, `exact` only
|
|
9
|
+
* exact suffix cycles, `semantic` exact cycles plus the near-duplicate and
|
|
10
|
+
* progress-lexicon paragraph heuristics.
|
|
11
11
|
*/
|
|
12
|
-
export
|
|
12
|
+
export type LoopGuardLevel = "off" | "exact" | "semantic";
|
|
13
|
+
/**
|
|
14
|
+
* Loop-guard level for a model stream. `PI_NO_THINKING_LOOP_GUARD=1` and
|
|
15
|
+
* `loopGuard.enabled: false` turn it off; otherwise the model line's
|
|
16
|
+
* `thinking-loop-guard` catalog axis decides, and unassigned lines get `exact`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loopGuardLevel(model: Model<Api>, options?: StreamOptions): LoopGuardLevel;
|
|
13
19
|
/**
|
|
14
20
|
* Stateful detector fed the streamed thinking deltas. `push` returns a
|
|
15
21
|
* human-readable reason the first time a loop shape is recognized; the caller
|
|
@@ -75,11 +81,10 @@ export declare class GeminiHeaderRunDetector {
|
|
|
75
81
|
*/
|
|
76
82
|
export declare function guardThinkingLoopStream(inner: AssistantMessageEventStream, model: Model<Api>, controller: AbortController, options?: StreamOptions): AssistantMessageEventStream;
|
|
77
83
|
/**
|
|
78
|
-
* Apply the loop guard around a provider dispatch
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* the upstream, then wraps the returned stream. Bounding result-path re-samples
|
|
84
|
+
* Apply the loop guard around a provider dispatch at the model's
|
|
85
|
+
* {@link loopGuardLevel}; `off` dispatches unwrapped. The guard injects an
|
|
86
|
+
* abort signal into the provider call so a detected loop tears down the
|
|
87
|
+
* upstream, then wraps the returned stream. Bounding result-path re-samples
|
|
83
88
|
* lives in the result-awaiting caller.
|
|
84
89
|
*/
|
|
85
90
|
export declare function withThinkingLoopGuard<O extends {
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export declare function stripOpenAIResponsesOutputOnlyStatusesForReplay<TItem ex
|
|
|
34
34
|
type?: unknown;
|
|
35
35
|
status?: unknown;
|
|
36
36
|
}>(items: TItem[]): TItem[];
|
|
37
|
+
/** Remove Codex-rejected backend metadata; preserve clean-item identity by default, or copy while keeping/dropping id. */
|
|
38
|
+
export declare function stripOpenAIResponsesInternalMetadataForReplay<TItem extends {
|
|
39
|
+
id?: unknown;
|
|
40
|
+
internal_chat_message_metadata_passthrough?: unknown;
|
|
41
|
+
}>(item: TItem, mode?: "preserve" | "copy" | "drop-id"): TItem;
|
|
37
42
|
/** Clamp replayed images without changing item identities or other native replay fields. */
|
|
38
43
|
export declare function clampOpenAIResponsesImageDetailForReplay<TItem extends {
|
|
39
44
|
type?: unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.15.0",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -155,11 +155,11 @@
|
|
|
155
155
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
156
156
|
},
|
|
157
157
|
"dependencies": {
|
|
158
|
-
"@oh-my-pi/omptype": "18.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.
|
|
158
|
+
"@oh-my-pi/omptype": "18.15.0",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.15.0",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.15.0",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.15.0",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.15.0"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
package/src/auth/refresh.ts
CHANGED
|
@@ -367,10 +367,7 @@ export class OAuthRefresher {
|
|
|
367
367
|
this.#armShutdownDrain();
|
|
368
368
|
const settled = () => {
|
|
369
369
|
this.#pendingRefreshes.delete(pending);
|
|
370
|
-
if (this.#pendingRefreshes.size === 0 && !this.#shuttingDown)
|
|
371
|
-
this.#cancelRefreshCleanup?.();
|
|
372
|
-
this.#cancelRefreshCleanup = undefined;
|
|
373
|
-
}
|
|
370
|
+
if (this.#pendingRefreshes.size === 0 && !this.#shuttingDown) this.#disarmShutdownDrain();
|
|
374
371
|
};
|
|
375
372
|
void pending.then(settled, settled);
|
|
376
373
|
return pending;
|
|
@@ -389,12 +386,41 @@ export class OAuthRefresher {
|
|
|
389
386
|
});
|
|
390
387
|
}
|
|
391
388
|
|
|
389
|
+
#disarmShutdownDrain(): void {
|
|
390
|
+
this.#cancelRefreshCleanup?.();
|
|
391
|
+
this.#cancelRefreshCleanup = undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
392
394
|
async #drainRefreshes(): Promise<void> {
|
|
393
395
|
this.#shuttingDown = true;
|
|
394
396
|
// Work tracked while draining (a lease hand-back a returning caller left behind) is waited for too.
|
|
395
397
|
while (this.#pendingRefreshes.size > 0) await Promise.allSettled(this.#pendingRefreshes);
|
|
396
398
|
}
|
|
397
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Stop admitting refreshes, then run `closeStore` once tracked work settles:
|
|
402
|
+
* a token exchange already under way, its save, a lease hand-back. Runs it at
|
|
403
|
+
* once when nothing is in flight. Closing the store under that work would
|
|
404
|
+
* lose a refresh token the provider already rotated. Called by
|
|
405
|
+
* {@link AuthStorage.close} and {@link AuthStorage.replaceStore}.
|
|
406
|
+
*/
|
|
407
|
+
close(closeStore: () => void): void {
|
|
408
|
+
this.#shuttingDown = true;
|
|
409
|
+
if (this.#pendingRefreshes.size === 0) {
|
|
410
|
+
this.#disarmShutdownDrain();
|
|
411
|
+
closeStore();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
void this.#drainRefreshes().then(() => {
|
|
415
|
+
this.#disarmShutdownDrain();
|
|
416
|
+
try {
|
|
417
|
+
closeStore();
|
|
418
|
+
} catch (error) {
|
|
419
|
+
logger.warn("Credential store close after OAuth refresh drain failed", { error: String(error) });
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
398
424
|
async #refreshStored<T extends OAuthCredential = OAuthCredential>(
|
|
399
425
|
provider: string,
|
|
400
426
|
options: StoredOAuthRefreshOptions<T>,
|
|
@@ -884,7 +910,12 @@ export class OAuthRefresher {
|
|
|
884
910
|
return "disabled";
|
|
885
911
|
}
|
|
886
912
|
|
|
887
|
-
|
|
913
|
+
/**
|
|
914
|
+
* Refresh `credential` unless it is still fresh. Shutdown waits for the
|
|
915
|
+
* refresh, and for the caller's synchronous save of its result, before the
|
|
916
|
+
* store closes.
|
|
917
|
+
*/
|
|
918
|
+
refresh(
|
|
888
919
|
provider: Provider,
|
|
889
920
|
credential: OAuthCredential,
|
|
890
921
|
credentialId: number | undefined,
|
|
@@ -892,13 +923,14 @@ export class OAuthRefresher {
|
|
|
892
923
|
reason?: OAuthRefreshReason,
|
|
893
924
|
rejectedAccess?: string,
|
|
894
925
|
): Promise<OAuthCredentials> {
|
|
926
|
+
if (this.#shuttingDown) return Promise.reject(new AIError.AbortError("OAuth refresh stopped during shutdown"));
|
|
895
927
|
const authRecovery = reason === "auth-recovery";
|
|
896
928
|
if (authRecovery && credentialId !== undefined && !this.#oauthCredentialRefreshInFlight.has(credentialId)) {
|
|
897
929
|
const recent = this.#recentMint(credentialId);
|
|
898
930
|
// A token the provider has just rejected is not a usable recent mint.
|
|
899
|
-
if (recent && recent.credential.access !== rejectedAccess) return recent.credential;
|
|
931
|
+
if (recent && recent.credential.access !== rejectedAccess) return Promise.resolve(recent.credential);
|
|
900
932
|
}
|
|
901
|
-
return this.#refreshSingleFlight(provider, credential, credentialId, signal, reason, rejectedAccess);
|
|
933
|
+
return this.#track(this.#refreshSingleFlight(provider, credential, credentialId, signal, reason, rejectedAccess));
|
|
902
934
|
}
|
|
903
935
|
|
|
904
936
|
/** Refresh without recent-mint reuse; still shares the per-credential in-flight request. */
|
|
@@ -947,7 +979,8 @@ export class OAuthRefresher {
|
|
|
947
979
|
.finally(() => {
|
|
948
980
|
this.#oauthCredentialRefreshInFlight.delete(credentialId);
|
|
949
981
|
});
|
|
950
|
-
|
|
982
|
+
// Tracked apart from the caller: a refresh its caller abandoned still has to finish before the store closes.
|
|
983
|
+
this.#oauthCredentialRefreshInFlight.set(credentialId, this.#track(promise));
|
|
951
984
|
return raceSignal(promise, signal, "credential refresh aborted");
|
|
952
985
|
}
|
|
953
986
|
|
|
@@ -973,7 +1006,9 @@ export class OAuthRefresher {
|
|
|
973
1006
|
// `refreshStored` owns the caller budget and the exchange backstop.
|
|
974
1007
|
refresh: (current, refreshSignal) =>
|
|
975
1008
|
this.#requestOAuthCredentialRefresh(provider, current, credentialId, refreshSignal, reason, undefined),
|
|
976
|
-
|
|
1009
|
+
// A usage probe is advisory: leave a dead grant for an interactive request to disable.
|
|
1010
|
+
isDefinitiveFailure:
|
|
1011
|
+
reason === "usage-probe" ? undefined : error => AIError.isDefinitiveOAuthFailure(String(error)),
|
|
977
1012
|
disabledCause: error => `oauth refresh failed: ${String(error)}`,
|
|
978
1013
|
});
|
|
979
1014
|
if (result.credential) {
|
|
@@ -1088,6 +1123,7 @@ export class OAuthRefresher {
|
|
|
1088
1123
|
signal?: AbortSignal,
|
|
1089
1124
|
options?: OAuthRefreshByIdOptions,
|
|
1090
1125
|
): Promise<AuthCredentialSnapshotEntry> {
|
|
1126
|
+
if (this.#shuttingDown) throw new AIError.AbortError("OAuth refresh stopped during shutdown");
|
|
1091
1127
|
const existing = this.#oauthRefreshInFlight.get(id);
|
|
1092
1128
|
if (existing) {
|
|
1093
1129
|
const joined = await raceSignal(existing, signal, "credential refresh aborted");
|
|
@@ -1122,7 +1158,8 @@ export class OAuthRefresher {
|
|
|
1122
1158
|
this.#oauthRefreshInFlight.delete(id);
|
|
1123
1159
|
}
|
|
1124
1160
|
})();
|
|
1125
|
-
|
|
1161
|
+
// Saves by id itself; tracked so the store stays open until that save lands.
|
|
1162
|
+
this.#oauthRefreshInFlight.set(id, this.#track(promise));
|
|
1126
1163
|
return raceSignal(promise, signal, "credential refresh aborted");
|
|
1127
1164
|
}
|
|
1128
1165
|
|
|
@@ -1164,7 +1201,8 @@ export class OAuthRefresher {
|
|
|
1164
1201
|
// A definitively-dead grant tears the row down here, where the
|
|
1165
1202
|
// attempted credential is known. CAS on the persisted credential so a
|
|
1166
1203
|
// peer/login rotation in flight leaves the freshly-rotated row intact.
|
|
1167
|
-
|
|
1204
|
+
// A usage probe is advisory: leave a dead grant for an interactive request to disable.
|
|
1205
|
+
if (reason !== "usage-probe" && AIError.isDefinitiveOAuthFailure(String(error))) {
|
|
1168
1206
|
// CAS-loss (false) means a peer/login rotated the row mid-refresh, so
|
|
1169
1207
|
// our #data copy is stale — reload so the next caller serves the
|
|
1170
1208
|
// freshly-rotated credential rather than the dead token we attempted.
|