@oh-my-pi/pi-ai 18.2.1 → 18.2.3
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 +25 -0
- package/README.md +2 -0
- package/dist/types/auth/sqlite-credential-store.d.ts +2 -1
- package/dist/types/auth-gateway/session-state.d.ts +69 -16
- package/dist/types/auth-storage.d.ts +11 -6
- package/dist/types/index.d.ts +1 -0
- package/dist/types/provider-session-state.d.ts +46 -0
- package/dist/types/providers/amazon-bedrock.d.ts +3 -0
- package/dist/types/providers/aws-sigv4.d.ts +12 -0
- package/dist/types/providers/openai-responses.d.ts +15 -0
- package/dist/types/registry/oauth/github-copilot.d.ts +2 -6
- package/dist/types/registry/oauth/kimi.d.ts +2 -1
- package/dist/types/registry/oauth/types.d.ts +2 -0
- package/dist/types/usage/openai-codex.d.ts +3 -1
- package/dist/types/usage.d.ts +3 -1
- package/package.json +6 -6
- package/src/auth/sqlite-credential-store.ts +8 -33
- package/src/auth-gateway/server.ts +159 -84
- package/src/auth-gateway/session-state.ts +227 -29
- package/src/auth-storage.ts +19 -9
- package/src/index.ts +1 -0
- package/src/provider-session-state.ts +56 -0
- package/src/providers/amazon-bedrock.ts +20 -3
- package/src/providers/anthropic-messages-server.ts +80 -20
- package/src/providers/anthropic-signature.ts +5 -2
- package/src/providers/aws-sigv4.ts +16 -5
- package/src/providers/cursor.ts +53 -9
- package/src/providers/openai-completions.ts +6 -0
- package/src/providers/openai-responses.ts +27 -0
- package/src/registry/oauth/github-copilot.ts +2 -2
- package/src/registry/oauth/kimi.ts +4 -4
- package/src/registry/oauth/types.ts +2 -0
- package/src/stream.ts +36 -1
- package/src/usage/openai-codex.ts +94 -11
- package/src/usage.ts +5 -1
- package/src/utils/http-inspector.ts +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.3] - 2026-09-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `stream()` and `streamSimple()` support asynchronous model header resolution for each request attempt, including authentication retries and cancellation.
|
|
10
|
+
- Provider login prompts can request masked entry with `secret: true`.
|
|
11
|
+
|
|
12
|
+
## [18.2.2] - 2026-09-16
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Added configurable `baseUrl` support for `bedrock-converse-stream` requests, enabling Amazon Bedrock providers and compatible custom providers to use VPC or PrivateLink endpoints, FIPS hosts, and internal gateways, including endpoints mounted under a path or authenticated with query parameters.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Corrupt credential databases are now backed up privately and recreated instead of preventing startup; signing in again restores credentials.
|
|
21
|
+
- Fixed malformed Anthropic thinking signatures that could freeze sessions at 100% CPU.
|
|
22
|
+
- Fixed Anthropic-compatible gateway tool-call handling so client-declared tools are reported with `stop_reason: "tool_use"` and already-executed native provider tools are not exposed for clients to run again.
|
|
23
|
+
- Fixed gateway session isolation when clients omit a session key, preventing one conversation's retained provider state from affecting another.
|
|
24
|
+
- Fixed retained provider state across credential switches so account-specific capabilities are re-evaluated while reusable endpoint capabilities remain available.
|
|
25
|
+
- Fixed session retention limits closing provider state while a request is still streaming.
|
|
26
|
+
- ChatGPT accounts that have exhausted a plan's usage window but still have available Codex credit can now continue to be selected for Codex requests.
|
|
27
|
+
- Cursor requests now honor explicit max-mode markers on wire-backed models instead of inferring the mode from the model suffix.
|
|
28
|
+
- OpenAI-compatible chat responses containing only structured tool calls now report time to first token correctly.
|
|
29
|
+
|
|
5
30
|
## [18.2.1] - 2026-09-15
|
|
6
31
|
|
|
7
32
|
### Added
|
package/README.md
CHANGED
|
@@ -1084,6 +1084,8 @@ omp auth-broker logout # interactive — pick a stored credential to
|
|
|
1084
1084
|
|
|
1085
1085
|
Credentials are saved to `agent.db` in the agent directory. `/login qianfan` opens the Qianfan console and stores the pasted API key.
|
|
1086
1086
|
|
|
1087
|
+
If SQLite reports corruption during startup, the damaged database and remaining journal sidecars are preserved beside it as private `agent.db.corrupt-<timestamp>-<id>*` backups before a fresh database is created. The log records the backup path. This restores startup, not unreadable credentials: log in again; retain the backups for manual data recovery. Lock contention and other non-corruption errors never reset the database.
|
|
1088
|
+
|
|
1087
1089
|
`login` supports OAuth providers (Anthropic, OpenAI Codex, GitHub Copilot, Gemini CLI, Antigravity) and API-key onboarding flows.
|
|
1088
1090
|
|
|
1089
1091
|
For the current API-key onboarding flows, the library covers Together, Moonshot, Qianfan, NVIDIA, NanoGPT, Novita, DeepInfra, Hugging Face, Venice, Xiaomi, vLLM, LiteLLM, Cloudflare AI Gateway, Qwen Portal, and Ollama Cloud. Ollama remains the local runtime integration; set `OLLAMA_API_KEY` only when your local or self-hosted deployment enforces bearer auth.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The public AuthCredentialStore interface remains in ../auth-storage so local
|
|
5
5
|
* and remote stores share the same contract.
|
|
6
6
|
*/
|
|
7
|
-
import { Database } from "bun:sqlite";
|
|
7
|
+
import type { Database } from "bun:sqlite";
|
|
8
8
|
import { isSqliteBusyError, isSqliteCorruptionError } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { AuthCredential, AuthCredentialStore, CredentialRefreshLeaseFence, DisabledCredentialSummary, StoredAuthCredential, StoredCredentialBlock } from "../auth-storage.js";
|
|
10
10
|
import type { OAuthCredentials } from "../registry/oauth/types.js";
|
|
@@ -29,6 +29,7 @@ export declare function resolveCredentialIdentityKey(provider: string, credentia
|
|
|
29
29
|
export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
30
30
|
#private;
|
|
31
31
|
constructor(db: Database);
|
|
32
|
+
/** Opens credential storage with bounded busy retries and one-shot corruption recovery. */
|
|
32
33
|
static open(dbPath?: string): Promise<SqliteAuthCredentialStore>;
|
|
33
34
|
listAuthCredentials(provider?: string): StoredAuthCredential[];
|
|
34
35
|
listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]>;
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
* A plain `Map<sessionId, …>` in a long-lived server process is a leak: nothing
|
|
20
20
|
* ever reclaims an entry, and the entries own timers and sockets. This store is
|
|
21
21
|
* an LRU with a hard entry ceiling that calls `close()` on everything it drops
|
|
22
|
-
* and on everything it still holds at shutdown
|
|
22
|
+
* and on everything it still holds at shutdown — but it only ever drops an
|
|
23
|
+
* entry no request is holding, because `close()` on a live entry tears down
|
|
24
|
+
* state an in-flight stream is still streaming through.
|
|
23
25
|
*/
|
|
24
|
-
import type { Api, Model, ProviderSessionState } from "../types.js";
|
|
26
|
+
import type { Api, Context, Model, ProviderSessionState } from "../types.js";
|
|
25
27
|
/**
|
|
26
28
|
* Retained logical sessions. Each entry is a handful of small provider records
|
|
27
29
|
* plus, for Codex, a WebSocket session — cheap to keep, but not free, so the
|
|
@@ -30,11 +32,58 @@ import type { Api, Model, ProviderSessionState } from "../types.js";
|
|
|
30
32
|
* have been quiet longer than the 256 most recent ones.
|
|
31
33
|
*/
|
|
32
34
|
export declare const AUTH_GATEWAY_MAX_SESSION_STATES = 256;
|
|
35
|
+
/**
|
|
36
|
+
* One request's claim on a retained session.
|
|
37
|
+
*
|
|
38
|
+
* `release()` is what makes the entry evictable again, so it MUST run for every
|
|
39
|
+
* outcome of the request — a `finally` at the call site for the synchronous
|
|
40
|
+
* paths, stream completion for the streaming ones. It is idempotent, so the
|
|
41
|
+
* two can overlap.
|
|
42
|
+
*/
|
|
43
|
+
export interface AuthGatewaySessionStateLease {
|
|
44
|
+
/** The map to hand to `streamSimple` as `providerSessionState`. */
|
|
45
|
+
readonly states: Map<string, ProviderSessionState>;
|
|
46
|
+
/** Reset account-scoped records if an in-request auth retry switches accounts. */
|
|
47
|
+
updateAccount(account: string): void;
|
|
48
|
+
/** Give up this request's claim. Idempotent. */
|
|
49
|
+
release(): void;
|
|
50
|
+
}
|
|
51
|
+
/** Everything the store needs to place one request on a retained session. */
|
|
52
|
+
export interface AuthGatewaySessionStateRequest {
|
|
53
|
+
/**
|
|
54
|
+
* The client's own session key (`prompt_cache_key` / `sessionId`), or
|
|
55
|
+
* `undefined` when it sent none — blank counts as none. A supplied key is
|
|
56
|
+
* authoritative: the client is telling us which conversation this is.
|
|
57
|
+
*/
|
|
58
|
+
clientKey: string | undefined;
|
|
59
|
+
model: Model<Api>;
|
|
60
|
+
/**
|
|
61
|
+
* System prompt, tools and message history of this request. Used only when
|
|
62
|
+
* `clientKey` is absent, to place the request on the conversation it
|
|
63
|
+
* continues.
|
|
64
|
+
*/
|
|
65
|
+
context: Context;
|
|
66
|
+
/**
|
|
67
|
+
* Stable identity of the account this request's credential resolved to.
|
|
68
|
+
* A change means the gateway switched the session to a sibling credential,
|
|
69
|
+
* so the account-dependent lessons in the retained map are re-probed. The
|
|
70
|
+
* comparison happens on acquire and whenever an in-request auth retry
|
|
71
|
+
* resolves a sibling credential.
|
|
72
|
+
*/
|
|
73
|
+
account: string;
|
|
74
|
+
}
|
|
33
75
|
/**
|
|
34
76
|
* Bounded per-session provider state, owned by one gateway server instance.
|
|
35
77
|
*
|
|
36
78
|
* Two gateways in the same process get separate stores, so neither can hand a
|
|
37
79
|
* request another gateway's learned state or close it out from under one.
|
|
80
|
+
*
|
|
81
|
+
* The recency order is this class's own (a `Map` iterates in insertion order,
|
|
82
|
+
* and every acquire re-inserts) rather than `LRUCache`'s, because the policy
|
|
83
|
+
* needs two things a general cache cannot express: an entry that a request is
|
|
84
|
+
* still holding must be skipped when picking a victim, and an entry must be
|
|
85
|
+
* able to change key — `LRUCache` disposes on every removal, which is precisely
|
|
86
|
+
* the `close()` we must not run here.
|
|
38
87
|
*/
|
|
39
88
|
export declare class AuthGatewaySessionStateStore {
|
|
40
89
|
#private;
|
|
@@ -42,24 +91,28 @@ export declare class AuthGatewaySessionStateStore {
|
|
|
42
91
|
/** Retained logical sessions. */
|
|
43
92
|
get size(): number;
|
|
44
93
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* next request.
|
|
94
|
+
* Claim the provider-session map for one request, created on first use and
|
|
95
|
+
* returned by reference so provider mutations persist into the next request.
|
|
48
96
|
*
|
|
49
|
-
* Keyed by
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* whose learning is endpoint-specific already sub-keys it internally
|
|
97
|
+
* Keyed by provider + model + conversation (see {@link sessionKeys}). A
|
|
98
|
+
* client is free to reuse one session id across models, and the coarsest
|
|
99
|
+
* provider entries do not separate models themselves (`openai-responses`
|
|
100
|
+
* keys its strict-tools / history-replay record by provider alone,
|
|
101
|
+
* Antigravity by a single constant), so the model belongs in the key here.
|
|
102
|
+
* Endpoint is deliberately absent: every provider whose learning is
|
|
103
|
+
* endpoint-specific already sub-keys it internally
|
|
57
104
|
* (`anthropic-messages:${baseUrl}\0${modelId}`,
|
|
58
105
|
* `openai-completions:${provider}:${baseUrl}:${modelId}`), and repeating it
|
|
59
|
-
* would only fragment the map.
|
|
60
|
-
*
|
|
106
|
+
* would only fragment the map. The credential is absent for the same reason
|
|
107
|
+
* — most of what is retained is true of the endpoint whoever calls it, and
|
|
108
|
+
* Codex already sub-keys its transport by account and bearer — so a
|
|
109
|
+
* credential switch resets the account-dependent subset instead of
|
|
110
|
+
* splitting the entry (see `resetAccountScopedProviderSessionState`).
|
|
111
|
+
*
|
|
112
|
+
* The returned lease MUST be released; until then the entry cannot be
|
|
113
|
+
* evicted.
|
|
61
114
|
*/
|
|
62
|
-
acquire(
|
|
115
|
+
acquire(request: AuthGatewaySessionStateRequest): AuthGatewaySessionStateLease;
|
|
63
116
|
/** Close and drop every retained state. Called when the gateway shuts down. */
|
|
64
117
|
close(): void;
|
|
65
118
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApiKeyResolver } from "./auth-retry.js";
|
|
2
|
-
import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthProviderId } from "./registry/oauth/types.js";
|
|
2
|
+
import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthPrompt, OAuthProviderId } from "./registry/oauth/types.js";
|
|
3
3
|
import type { Provider } from "./types.js";
|
|
4
4
|
import type { ClientUsageIdentity, ClientUsageReport, ClientUsageSummary, CredentialRankingStrategy, ObservedUsageEntry, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage.js";
|
|
5
5
|
import { type CodexResetConsumeCode, type CodexResetCredit } from "./usage/openai-codex-reset.js";
|
|
@@ -800,7 +800,7 @@ export declare class AuthStorage {
|
|
|
800
800
|
* Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
|
|
801
801
|
* still wins for the duration of a single invocation.
|
|
802
802
|
*/
|
|
803
|
-
setConfigApiKey(provider: string,
|
|
803
|
+
setConfigApiKey(provider: string, apiKeyConfig: string): void;
|
|
804
804
|
/**
|
|
805
805
|
* Remove a single config-sourced API key override.
|
|
806
806
|
*/
|
|
@@ -815,6 +815,13 @@ export declare class AuthStorage {
|
|
|
815
815
|
* Used for custom provider keys from models.json.
|
|
816
816
|
*/
|
|
817
817
|
setFallbackResolver(resolver: (provider: string) => string | undefined): void;
|
|
818
|
+
/**
|
|
819
|
+
* Install the host's async config-value resolver. Coding-agent uses this so
|
|
820
|
+
* every stored/config credential reference shares command caching,
|
|
821
|
+
* failure backoff, and process hardening even when AuthStorage was created
|
|
822
|
+
* independently and later attached to a registry.
|
|
823
|
+
*/
|
|
824
|
+
setConfigValueResolver(resolver: (config: string) => Promise<string | undefined>): void;
|
|
818
825
|
/**
|
|
819
826
|
* Reload credentials from storage.
|
|
820
827
|
*/
|
|
@@ -935,10 +942,7 @@ export declare class AuthStorage {
|
|
|
935
942
|
/** onAuth is required by auth-storage but optional in OAuthController */
|
|
936
943
|
onAuth: (info: OAuthAuthInfo) => void;
|
|
937
944
|
/** onPrompt is required for some providers (github-copilot, openai-codex) */
|
|
938
|
-
onPrompt: (prompt:
|
|
939
|
-
message: string;
|
|
940
|
-
placeholder?: string;
|
|
941
|
-
}) => Promise<string>;
|
|
945
|
+
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
942
946
|
}): Promise<OAuthLoginIdentity | undefined>;
|
|
943
947
|
/**
|
|
944
948
|
* Logout from a provider.
|
|
@@ -976,6 +980,7 @@ export declare class AuthStorage {
|
|
|
976
980
|
ingestUsageHeaders(provider: Provider, headers: Record<string, string>, options?: {
|
|
977
981
|
sessionId?: string;
|
|
978
982
|
baseUrl?: string;
|
|
983
|
+
responseStatus?: number;
|
|
979
984
|
}): boolean;
|
|
980
985
|
/**
|
|
981
986
|
* The {@link UsageProvider} registered for `provider`, or undefined when the
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./auth-storage.js";
|
|
|
8
8
|
export * from "./error/rate-limit.js";
|
|
9
9
|
export * from "./oneshot-retry.js";
|
|
10
10
|
export * from "./provider-details.js";
|
|
11
|
+
export * from "./provider-session-state.js";
|
|
11
12
|
export * from "./providers/anthropic.js";
|
|
12
13
|
export * from "./providers/anthropic-client.js";
|
|
13
14
|
export * from "./providers/azure-openai-responses.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential-rotation handling for a retained `providerSessionState` map.
|
|
3
|
+
*
|
|
4
|
+
* A host that keeps one provider-session map per logical conversation (the
|
|
5
|
+
* auth-gateway's server-owned store, an in-process omp session) can outlive the
|
|
6
|
+
* credential that filled it: `AuthStorage.markUsageLimitReached` and the
|
|
7
|
+
* auth-retry resolver both switch a session to a sibling account mid-flight.
|
|
8
|
+
* Most of what a provider learns is a property of the *endpoint*, so rebuilding
|
|
9
|
+
* the whole map on a switch would re-pay every rejected round-trip the map
|
|
10
|
+
* exists to avoid. A minority is a property of the *account*, and keeping that
|
|
11
|
+
* across a switch is a bug.
|
|
12
|
+
*
|
|
13
|
+
* Audit of what the retained records hold, per provider:
|
|
14
|
+
*
|
|
15
|
+
* - **Anthropic** — `fastModeDisabled` is account-scoped: the rejection reads
|
|
16
|
+
* "this model does not support fast mode for your account", i.e. a plan
|
|
17
|
+
* entitlement, so a switch to an entitled sibling must re-probe. Its
|
|
18
|
+
* siblings are endpoint-scoped and stay: `strictToolsDisabled`
|
|
19
|
+
* (grammar-too-large 400 for the model's tool schema),
|
|
20
|
+
* `replayUnsignedThinkingDisabled` / `thinkingReplayDisabled` (the endpoint
|
|
21
|
+
* is a signing proxy), `prefixDroppedThinkingBlocks` (blocks the API itself
|
|
22
|
+
* dropped), `controlStates` (per-conversation control baselines).
|
|
23
|
+
* - **OpenAI Responses** — the `previous_response_id` chain baselines are
|
|
24
|
+
* account-scoped: a stored response belongs to the account that created it.
|
|
25
|
+
* Strict-tools / reasoning-effort fallbacks, replay warmup and the chaining
|
|
26
|
+
* circuit breaker are endpoint-scoped and stay.
|
|
27
|
+
* - **OpenAI Completions** — strict-tools and reasoning-effort fallbacks only;
|
|
28
|
+
* both endpoint-scoped. Nothing to reset.
|
|
29
|
+
* - **Codex** — already sub-keys its WebSocket sessions by account id AND
|
|
30
|
+
* bearer (`getCodexWebSocketSessionKey`), so a switch naturally lands on a
|
|
31
|
+
* fresh transport session while the old one stays reachable for teardown.
|
|
32
|
+
* Resetting from the outside would close a socket a retry may still be on.
|
|
33
|
+
* - **Antigravity** — `lastGoodEndpoint` is endpoint-scoped; the agent /
|
|
34
|
+
* conversation ids are conversation-scoped. Neither depends on the account.
|
|
35
|
+
* - **GitLab Duo** — the active workflow is account-bound, but it is a live
|
|
36
|
+
* server-side workflow plus socket, and the switch happens *inside* the
|
|
37
|
+
* request that may still be resuming it. Tearing it down here would abort the
|
|
38
|
+
* very turn that rotated; it stays on its existing session-close path.
|
|
39
|
+
*/
|
|
40
|
+
import type { ProviderSessionState } from "./types.js";
|
|
41
|
+
/**
|
|
42
|
+
* Reset the account-dependent lessons in `states`, keeping everything a
|
|
43
|
+
* provider learned about the endpoint. Call when a retained map is about to be
|
|
44
|
+
* reused for a session whose credential now resolves to a different account.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resetAccountScopedProviderSessionState(states: Map<string, ProviderSessionState>): void;
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* SigV4 signing and decodes the `application/vnd.amazon.eventstream` response.
|
|
6
6
|
* No `@aws-sdk/*`, no `@smithy/*`, no `proxy-agent`. Proxies are honored via
|
|
7
7
|
* Bun's native `HTTPS_PROXY` support.
|
|
8
|
+
*
|
|
9
|
+
* A `models.yml` `baseUrl` is the request origin verbatim (VPC endpoint, gateway, …);
|
|
10
|
+
* only AWS's own regional host is re-pointed at the resolved region. SigV4 unaffected.
|
|
8
11
|
*/
|
|
9
12
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
10
13
|
import type { StreamFunction, StreamOptions, ThinkingBudgets } from "../types.js";
|
|
@@ -45,6 +45,18 @@ export declare function formatAmzDate(d: Date): {
|
|
|
45
45
|
longDate: string;
|
|
46
46
|
shortDate: string;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* AWS's canonical-request spec encodes each name/value first, THEN sorts by
|
|
50
|
+
* the encoded form ("Sort the encoded parameter names by character code" —
|
|
51
|
+
* https://docs.aws.amazon.com/IAM/latest/UserGuide/create-canonical-request.html).
|
|
52
|
+
* Sorting the decoded form instead gives the wrong order whenever encoding
|
|
53
|
+
* changes a character's relative position — e.g. raw key `%7B` (decodes to
|
|
54
|
+
* `{`, 0x7B) vs `x` (0x78): decoded, `x` < `{`; encoded, `%` (0x25) < `x`, so
|
|
55
|
+
* `%7B` sorts first. A gateway that validates SigV4 (or AWS itself) computes
|
|
56
|
+
* the signature over ITS OWN canonicalization and rejects ours if the two
|
|
57
|
+
* disagree on order.
|
|
58
|
+
*/
|
|
59
|
+
export declare function canonicalQuery(query: string | undefined): string;
|
|
48
60
|
export interface SignedHeaders {
|
|
49
61
|
host: string;
|
|
50
62
|
"x-amz-date": string;
|
|
@@ -85,6 +85,21 @@ interface OpenAIResponsesChainState {
|
|
|
85
85
|
/** Set once chaining is judged unsupported for this session (circuit breaker). */
|
|
86
86
|
disabled: boolean;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Drop the account-bound half of every retained `openai-responses` record in
|
|
90
|
+
* `states`: the stateful `previous_response_id` chain baselines.
|
|
91
|
+
*
|
|
92
|
+
* Chaining stores the turn server-side under the account that created it, so a
|
|
93
|
+
* baseline minted by one credential is dead weight the moment the session is
|
|
94
|
+
* switched to a sibling account — the next delta request answers
|
|
95
|
+
* `Previous response not found` and burns a turn re-learning that. Everything
|
|
96
|
+
* else this record holds describes the *deployment*, not the account
|
|
97
|
+
* (strict-tools demotion, reasoning-effort fallback, native-history-replay
|
|
98
|
+
* warmup, the chaining circuit breaker), and is deliberately preserved:
|
|
99
|
+
* re-learning an endpoint's limits on every credential switch is the cost this
|
|
100
|
+
* state exists to avoid.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resetOpenAIResponsesAccountScopedState(states: Map<string, ProviderSessionState>): void;
|
|
88
103
|
type OpenRouterAnthropicCacheControl = {
|
|
89
104
|
type: "ephemeral";
|
|
90
105
|
ttl?: "1h";
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { FetchImpl } from "../../types.js";
|
|
2
|
-
import type { OAuthController, OAuthCredentials } from "./types.js";
|
|
2
|
+
import type { OAuthController, OAuthCredentials, OAuthPrompt } from "./types.js";
|
|
3
3
|
type GitHubCopilotLoginOptions = {
|
|
4
4
|
onAuth: (url: string, instructions?: string) => void;
|
|
5
|
-
onPrompt: (prompt:
|
|
6
|
-
message: string;
|
|
7
|
-
placeholder?: string;
|
|
8
|
-
allowEmpty?: boolean;
|
|
9
|
-
}) => Promise<string>;
|
|
5
|
+
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
10
6
|
onProgress?: (message: string) => void;
|
|
11
7
|
copilotIntegrationId?: unknown;
|
|
12
8
|
signal?: AbortSignal;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Kimi Code OAuth flow (device authorization grant)
|
|
3
3
|
*/
|
|
4
|
-
|
|
4
|
+
/** Lazily resolve the process-stable device headers used by Kimi requests. */
|
|
5
|
+
export declare const getKimiCommonHeaders: () => Readonly<{
|
|
5
6
|
"User-Agent": `KimiCLI/${string}`;
|
|
6
7
|
"X-Msh-Platform": "kimi_cli";
|
|
7
8
|
"X-Msh-Version": string;
|
|
@@ -33,6 +33,8 @@ export type OAuthPrompt = {
|
|
|
33
33
|
message: string;
|
|
34
34
|
placeholder?: string;
|
|
35
35
|
allowEmpty?: boolean;
|
|
36
|
+
/** Request masked entry from interactive hosts. Hosts that cannot hide input must reject the prompt. */
|
|
37
|
+
secret?: boolean;
|
|
36
38
|
};
|
|
37
39
|
export type OAuthAuthInfo = {
|
|
38
40
|
/**
|
|
@@ -5,6 +5,8 @@ import type { CredentialRankingStrategy, UsageProvider, UsageReport } from "../u
|
|
|
5
5
|
* ingesting them lets credential selection block an exhausted account before
|
|
6
6
|
* the next request burns a wire 429.
|
|
7
7
|
*/
|
|
8
|
-
export declare function parseCodexRateLimitHeaders(headers: Record<string, string>, now?: number
|
|
8
|
+
export declare function parseCodexRateLimitHeaders(headers: Record<string, string>, now?: number, context?: {
|
|
9
|
+
responseStatus?: number;
|
|
10
|
+
}): UsageReport | null;
|
|
9
11
|
export declare const openaiCodexUsageProvider: UsageProvider;
|
|
10
12
|
export declare const codexRankingStrategy: CredentialRankingStrategy;
|
package/dist/types/usage.d.ts
CHANGED
|
@@ -480,7 +480,9 @@ export interface UsageProvider {
|
|
|
480
480
|
id: Provider;
|
|
481
481
|
fetchUsage(params: UsageFetchParams, ctx: UsageFetchContext): Promise<UsageReport | null>;
|
|
482
482
|
/** Parse provider rate-limit response headers (lowercased keys) into a usage report, if supported. */
|
|
483
|
-
parseRateLimitHeaders?(headers: Record<string, string>, now?: number
|
|
483
|
+
parseRateLimitHeaders?(headers: Record<string, string>, now?: number, context?: {
|
|
484
|
+
responseStatus?: number;
|
|
485
|
+
}): UsageReport | null;
|
|
484
486
|
supports?(params: UsageFetchParams): boolean;
|
|
485
487
|
/** True when fetchUsage contacts upstream and can authenticate the credential for health checks. */
|
|
486
488
|
validatesCredentials?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.2.
|
|
3
|
+
"version": "18.2.3",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
125
125
|
},
|
|
126
126
|
"dependencies": {
|
|
127
|
-
"@oh-my-pi/omptype": "18.2.
|
|
128
|
-
"@oh-my-pi/pi-catalog": "18.2.
|
|
129
|
-
"@oh-my-pi/pi-natives": "18.2.
|
|
130
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
131
|
-
"@oh-my-pi/pi-wire": "18.2.
|
|
127
|
+
"@oh-my-pi/omptype": "18.2.3",
|
|
128
|
+
"@oh-my-pi/pi-catalog": "18.2.3",
|
|
129
|
+
"@oh-my-pi/pi-natives": "18.2.3",
|
|
130
|
+
"@oh-my-pi/pi-utils": "18.2.3",
|
|
131
|
+
"@oh-my-pi/pi-wire": "18.2.3"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@types/bun": "^1.3.14"
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The public AuthCredentialStore interface remains in ../auth-storage so local
|
|
5
5
|
* and remote stores share the same contract.
|
|
6
6
|
*/
|
|
7
|
-
import { Database,
|
|
7
|
+
import type { Database, Statement } from "bun:sqlite";
|
|
8
8
|
import * as fs from "node:fs/promises";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isSqliteBusyError,
|
|
16
16
|
isSqliteCorruptionError,
|
|
17
17
|
logger,
|
|
18
|
+
openSqliteDatabase,
|
|
18
19
|
} from "@oh-my-pi/pi-utils";
|
|
19
20
|
import type {
|
|
20
21
|
AuthCredential,
|
|
@@ -25,7 +26,6 @@ import type {
|
|
|
25
26
|
StoredAuthCredential,
|
|
26
27
|
StoredCredentialBlock,
|
|
27
28
|
} from "../auth-storage";
|
|
28
|
-
import * as AIError from "../error";
|
|
29
29
|
import type { OAuthCredentials } from "../registry/oauth/types";
|
|
30
30
|
import type { Provider } from "../types";
|
|
31
31
|
import type {
|
|
@@ -509,6 +509,7 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
509
509
|
);
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
+
/** Opens credential storage with bounded busy retries and one-shot corruption recovery. */
|
|
512
513
|
static async open(dbPath: string = getAgentDbPath()): Promise<SqliteAuthCredentialStore> {
|
|
513
514
|
const dir = path.dirname(dbPath);
|
|
514
515
|
const dirExists = await fs
|
|
@@ -519,23 +520,9 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
519
520
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
520
521
|
}
|
|
521
522
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// exponential backoff before surfacing the failure. See issue #2421.
|
|
526
|
-
const maxAttempts = 4;
|
|
527
|
-
const baseDelayMs = 100;
|
|
528
|
-
let lastBusyError: Error | undefined;
|
|
529
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
530
|
-
let db: Database | undefined;
|
|
531
|
-
try {
|
|
532
|
-
db = new Database(dbPath);
|
|
533
|
-
// Install the busy handler BEFORE the first lock-taking statement
|
|
534
|
-
// on this connection. The leases DDL below and the constructor's
|
|
535
|
-
// schema init both acquire locks during WAL recovery; without a
|
|
536
|
-
// non-zero `busy_timeout` they fail immediately with SQLITE_BUSY.
|
|
537
|
-
// See issue #2421.
|
|
538
|
-
SqliteAuthCredentialStore.#installBusyTimeout(db);
|
|
523
|
+
return openSqliteDatabase(
|
|
524
|
+
dbPath,
|
|
525
|
+
async db => {
|
|
539
526
|
try {
|
|
540
527
|
await fs.chmod(dbPath, 0o600);
|
|
541
528
|
} catch {
|
|
@@ -543,20 +530,8 @@ export class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
543
530
|
}
|
|
544
531
|
SqliteAuthCredentialStore.#ensureAuthCredentialRefreshLeasesTable(db);
|
|
545
532
|
return new SqliteAuthCredentialStore(db);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
if (!isSqliteBusyError(err)) {
|
|
549
|
-
throw err;
|
|
550
|
-
}
|
|
551
|
-
lastBusyError = err instanceof Error ? err : new Error(String(err));
|
|
552
|
-
if (attempt < maxAttempts - 1) {
|
|
553
|
-
await Bun.sleep(baseDelayMs * 2 ** attempt);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
throw new AIError.ConfigurationError(
|
|
558
|
-
`Failed to open auth database at '${dbPath}' after ${maxAttempts} attempts: ${lastBusyError?.message}`,
|
|
559
|
-
{ cause: lastBusyError },
|
|
533
|
+
},
|
|
534
|
+
{ recoverCorruption: true },
|
|
560
535
|
);
|
|
561
536
|
}
|
|
562
537
|
|