@oh-my-pi/pi-ai 18.8.3 → 18.8.5
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 +42 -64
- package/dist/types/auth/affinity.d.ts +4 -3
- package/dist/types/auth/oauth.d.ts +4 -3
- package/dist/types/auth/refresh.d.ts +1 -0
- package/dist/types/auth/select.d.ts +1 -1
- package/dist/types/auth/store.d.ts +2 -2
- package/dist/types/auth/types.d.ts +38 -10
- package/dist/types/auth-broker/client.d.ts +1 -1
- package/dist/types/auth-broker/remote-store.d.ts +5 -4
- package/dist/types/auth-broker/server.d.ts +2 -0
- package/dist/types/auth-gateway/dispatch.d.ts +2 -0
- package/dist/types/auth-gateway/http.d.ts +0 -2
- package/dist/types/provider-session-state.d.ts +2 -2
- package/dist/types/providers/anthropic.d.ts +6 -0
- package/dist/types/providers/cursor.d.ts +7 -7
- package/dist/types/registry/oauth/cursor.d.ts +1 -1
- package/dist/types/usage.d.ts +2 -0
- package/dist/types/utils/anthropic-auth.d.ts +21 -7
- package/dist/types/utils/resolve-peer.d.ts +2 -0
- package/package.json +6 -6
- package/src/auth/affinity.ts +9 -3
- package/src/auth/health.ts +2 -2
- package/src/auth/oauth.ts +16 -4
- package/src/auth/refresh.ts +17 -9
- package/src/auth/resets.ts +1 -1
- package/src/auth/select.ts +17 -12
- package/src/auth/store.ts +2 -2
- package/src/auth/types.ts +46 -11
- package/src/auth/usage.ts +1 -1
- package/src/auth-broker/client.ts +5 -2
- package/src/auth-broker/remote-store.ts +39 -9
- package/src/auth-broker/server.ts +33 -7
- package/src/auth-gateway/dispatch.ts +2 -0
- package/src/auth-gateway/http.ts +0 -8
- package/src/auth-gateway/server.ts +10 -9
- package/src/auth-storage.ts +7 -1
- package/src/provider-session-state.ts +2 -2
- package/src/providers/anthropic.ts +58 -12
- package/src/providers/cursor.ts +35 -9
- package/src/registry/oauth/cursor.ts +25 -16
- package/src/usage/github-copilot.ts +1 -1
- package/src/usage.ts +8 -0
- package/src/utils/anthropic-auth.ts +34 -10
- package/src/utils/resolve-peer.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.8.5] - 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `oauth.refresh(id, signal, { reason: "auth-recovery" })` forwards provider-401 recovery intent to a delegated (auth broker) refresh, and `AuthStorageOptions.refreshOAuthCredentialMints` marks a `refreshOAuthCredential` hook that exchanges tokens itself so its tokens are reused for auth recovery ([#14752](https://github.com/can1357/oh-my-pi/pull/14752) by [@will-bogusz](https://github.com/will-bogusz))
|
|
10
|
+
- `SessionsApi.inherit` accepts an optional filter, called with each provider and whether the source's affinity is an explicit user pin, to copy only the affinities it accepts ([#14749](https://github.com/can1357/oh-my-pi/pull/14749) by [@will-bogusz](https://github.com/will-bogusz))
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `AuthApiKeyOptions.accountIds` also matches the login email, or else the project id, of credentials that carry no account id (see `oauthAccountKey`), so Antigravity requests prefer accounts that serve the requested model ([#14924](https://github.com/can1357/oh-my-pi/issues/14924)).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed accounts sitting exactly at their `reservePct` (e.g. 70% used with a 30% reserve) still being picked and reported healthy instead of being held in reserve ([#14765](https://github.com/can1357/oh-my-pi/pull/14765) by [@will-bogusz](https://github.com/will-bogusz))
|
|
19
|
+
- Fixed `oauth.accessById` with `forceRefresh` returning the stored token unchanged while it was still valid; it now re-mints that one account (through the auth broker when configured) and returns that account's token even if another row is removed meanwhile ([#14752](https://github.com/can1357/oh-my-pi/pull/14752) by [@will-bogusz](https://github.com/will-bogusz))
|
|
20
|
+
- Fixed `omp -p` and other short-lived auth-broker clients missing from `omp usage clients`: usage still waiting for the 10-second report batch is now sent to the broker before the process exits ([#14899](https://github.com/can1357/oh-my-pi/pull/14899) by [@will-bogusz](https://github.com/will-bogusz))
|
|
21
|
+
- Fixed Cursor web fetches that were cut off by a dropped connection disappearing from resumed and rebuilt sessions; they now show as interrupted ([#14819](https://github.com/can1357/oh-my-pi/pull/14819) by [@jchanghong023](https://github.com/jchanghong023))
|
|
22
|
+
- Anthropic hosted web search can honor custom providers' OAuth-style request shaping and configured headers consistently with conversations ([#14919](https://github.com/can1357/oh-my-pi/pull/14919) by [@farnoy](https://github.com/farnoy))
|
|
23
|
+
|
|
24
|
+
## [18.8.4] - 2026-10-08
|
|
25
|
+
|
|
26
|
+
### Breaking Changes
|
|
27
|
+
|
|
28
|
+
- `AuthBrokerClient.notifyUsageStale` and `UsageLedgerStore.invalidateUsageCache` (including `RemoteAuthCredentialStore.invalidateUsageCache`) now take an optional leading `provider` argument: the signature is `(provider?: string, signal?: AbortSignal)` instead of `(signal?: AbortSignal)` ([#14761](https://github.com/can1357/oh-my-pi/pull/14761) by [@will-bogusz](https://github.com/will-bogusz))
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- Auth gateway route option `excludeProviders` leaves those providers' accounts out of `/v1/usage` and `/v1/credentials/check` ([#14755](https://github.com/can1357/oh-my-pi/pull/14755) by [@will-bogusz](https://github.com/will-bogusz))
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Fixed a single-provider usage refresh from an auth broker client (`omp usage invalidate --provider`, Codex auto-redeem, a saved-reset redeem) wiping the broker's cached usage for every provider, so other accounts showed no usage on every client until their next successful probe ([#14761](https://github.com/can1357/oh-my-pi/pull/14761) by [@will-bogusz](https://github.com/will-bogusz))
|
|
37
|
+
- Fixed Anthropic sessions missing the prompt cache a second time after the API dropped thinking blocks from a changed conversation ([#14748](https://github.com/can1357/oh-my-pi/pull/14748) by [@will-bogusz](https://github.com/will-bogusz))
|
|
38
|
+
- Fixed Cursor logins never renewing: every refresh was rejected with "Invalid User API Key", so logins lapsed about 60 days after sign-in; a session Cursor has ended is now disabled with a re-login hint ([#14753](https://github.com/can1357/oh-my-pi/pull/14753) by [@will-bogusz](https://github.com/will-bogusz))
|
|
39
|
+
- Fixed the auth broker logging caller-supplied `X-Forwarded-For` / `X-Real-IP` values and unknown request paths; peers come from the socket unless `trustProxyHeaders` is set ([#14762](https://github.com/can1357/oh-my-pi/pull/14762) by [@will-bogusz](https://github.com/will-bogusz))
|
|
40
|
+
- Fixed false thinking-loop detections on Gemini, DeepSeek, and Grok when the model drafts or answers with repetitive code or markup (VRML, SVG, JSON); such output is no longer discarded and retried.
|
|
41
|
+
- Fixed Cursor provider leaking conversation checkpoints, blob stores and abort listeners after requests end ([#14669](https://github.com/can1357/oh-my-pi/pull/14669) by [@H4vC](https://github.com/H4vC))
|
|
42
|
+
- Fixed session-affinity pins growing without bound in long-lived gateways (now capped at 256 sessions per provider) ([#14672](https://github.com/can1357/oh-my-pi/pull/14672) by [@H4vC](https://github.com/H4vC))
|
|
43
|
+
- Fixed completed Cursor turns failing with "Cursor stream ended before turnEnded" when the connection closed after the answer had fully arrived ([#14851](https://github.com/can1357/oh-my-pi/pull/14851) by [@kyle-elliott-asymptote](https://github.com/kyle-elliott-asymptote)).
|
|
44
|
+
- Fixed Cursor provider errors that Cursor marks as not retryable being retried until the retry budget ran out ([#14851](https://github.com/can1357/oh-my-pi/pull/14851) by [@kyle-elliott-asymptote](https://github.com/kyle-elliott-asymptote)).
|
|
45
|
+
|
|
5
46
|
## [18.8.3] - 2026-10-07
|
|
6
47
|
|
|
7
48
|
### Added
|
|
@@ -2307,67 +2348,4 @@
|
|
|
2307
2348
|
- Fixed `validateToolArguments` silently accepting JSON-encoded array strings (e.g. `'["a","b"]'`) against `union(string, array<string>)` schemas — providers that double-serialize tool-call arguments (Z.AI / GLM) caused tools like `search` to receive the literal `["a","b"]` as a single path, producing zero matches (single element) or glob parse errors (multi-element). A new pre-validation pass parses JSON-array-shaped strings when the schema explicitly accepts both shapes. ([#1788](https://github.com/can1357/oh-my-pi/issues/1788))
|
|
2308
2349
|
- Fixed Anthropic thinking summaries that arrive wrapped in literal `<thinking>` tags so advisor/raw transcript dumps do not render nested thinking tags ([#2695](https://github.com/can1357/oh-my-pi/issues/2695)).
|
|
2309
2350
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
### Breaking Changes
|
|
2313
|
-
|
|
2314
|
-
- Renamed the public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect`.
|
|
2315
|
-
- Renamed grammar dialect identifiers from `ToolCallSyntax` to `Dialect`, renamed the `Grammar` interface to `DialectDefinition`, and renamed `Grammar.syntax` to `DialectDefinition.dialect`.
|
|
2316
|
-
- Added `DialectDefinition.renderThinking` and `DialectDefinition.renderTranscript` so dialect implementations serialize complete native chat transcripts, not just tool call/result blocks.
|
|
2317
|
-
|
|
2318
|
-
### Added
|
|
2319
|
-
|
|
2320
|
-
- Added `renderTranscript` method to dialect definitions for serializing complete native chat transcripts
|
|
2321
|
-
- Added `renderThinking` method to dialect definitions for rendering thinking/reasoning blocks
|
|
2322
|
-
- Added support for 11 dialect implementations: Anthropic, DeepSeek, Gemini, Gemma, GLM, Harmony, Hermes, Kimi, Pi-native, Qwen3, and XML
|
|
2323
|
-
- Added `createInbandScanner` factory function to instantiate dialect-specific scanners
|
|
2324
|
-
- Added `getDialectDefinition` function to retrieve dialect implementations by name
|
|
2325
|
-
- Added `renderToolCatalog` and `renderInbandToolPrompt` functions for tool catalog rendering
|
|
2326
|
-
- Added `renderToolInventory` function to generate human-readable per-tool documentation with examples
|
|
2327
|
-
- Added `renderToolExamples` function to render tool usage examples in the model's native dialect
|
|
2328
|
-
- Added `encodeInbandToolHistory` function to encode tool call history in dialect-specific format
|
|
2329
|
-
- Added `wrapInbandToolStream` function to process streaming responses with in-band tool call parsing
|
|
2330
|
-
- Added `ThinkingInbandScanner` for parsing thinking/reasoning blocks across dialects
|
|
2331
|
-
- Added `OwnedStream` class for managing dialect-aware streaming with tool call events
|
|
2332
|
-
- Added in-band thinking channels to every dialect that was missing one: `gemini` (a ` ```thinking ` fence mirroring ` ```tool_code `), `gemma` (its native `<|channel>thought…<channel|>` reasoning channel), `kimi` (`<think>…</think>`), and `pi` (`<thinking>…</thinking>`). Each scanner now parses reasoning into thinking events instead of leaking chain-of-thought into the visible reply, and every dialect's `renderThinking` is a real channel that round-trips back through its scanner (no passthrough renderers).
|
|
2333
|
-
|
|
2334
|
-
### Changed
|
|
2335
|
-
|
|
2336
|
-
- Moved public dialect entrypoint from `@oh-my-pi/pi-ai/grammar` to `@oh-my-pi/pi-ai/dialect` in package exports
|
|
2337
|
-
- Updated internal imports in `stream-markup-healing.ts` to use new dialect module path
|
|
2338
|
-
- Changed `renderToolInventory` to demote a tool description's own markdown headers by one level when it contains a top-level `# ` header, so they nest under the wrapping `# Tool: <name>` heading instead of reading as sibling sections. Descriptions that already start at `##` and headers inside fenced code blocks are left untouched.
|
|
2339
|
-
|
|
2340
|
-
### Fixed
|
|
2341
|
-
|
|
2342
|
-
- Fixed Gemini, Gemma, Kimi, and Pi in-band scanners to respect `parseThinking: false`, leaving private reasoning markers in visible text when parsing is disabled
|
|
2343
|
-
- Fixed thinking-channel parsing for streaming Gemini, Gemma, Kimi, and Pi outputs so split or partial `<thinking>` blocks no longer leak into visible replies
|
|
2344
|
-
- Fixed in-band thinking finalization and Kimi stream-healing interactions so leaked `<think>` blocks are preserved when structured tool calls are present, not duplicated when explicit reasoning is present, and closed on stream flush.
|
|
2345
|
-
|
|
2346
|
-
### Removed
|
|
2347
|
-
|
|
2348
|
-
- Removed `src/grammar/factory.ts` (replaced by `src/dialect/factory.ts`)
|
|
2349
|
-
- Removed `src/grammar/rendering.ts` (functionality moved to `src/dialect/rendering.ts`)
|
|
2350
|
-
- Removed `src/grammar/xml.ts` (replaced by `src/dialect/xml.ts`)
|
|
2351
|
-
|
|
2352
|
-
## [15.13.3] - 2026-06-15
|
|
2353
|
-
|
|
2354
|
-
### Added
|
|
2355
|
-
|
|
2356
|
-
- Added the `gemini` in-band tool-call syntax with Python-style `tool_code` blocks and `default_api` invocations
|
|
2357
|
-
- Added the `gemma` token-delimited in-band tool-call syntax using `<|tool_call>` and `<|tool_response>` blocks
|
|
2358
|
-
- Added `gemini` and `gemma` to owned stream tool-result token detection so their tool responses are recognized
|
|
2359
|
-
- Fixed truncated Gemini and Gemma tool blocks from being emitted as plain text during streaming
|
|
2360
|
-
- Added the Azure OpenAI provider definition (`azure`) to the registry; `AZURE_OPENAI_API_KEY` resolves as its env-var API key via the catalog provider table.
|
|
2361
|
-
|
|
2362
|
-
### Changed
|
|
2363
|
-
|
|
2364
|
-
- Gemini tool-call examples now render without the `default_api.` namespace prefix, keeping `<example>` blocks concise. The live wire format still uses `default_api.` per the Gemini grammar.
|
|
2365
|
-
|
|
2366
|
-
### Fixed
|
|
2367
|
-
|
|
2368
|
-
- Fixed duplicate tool call projections by deduplicating provider-native `toolCall` events against in-band `tool_code` calls and keeping only the first real channel
|
|
2369
|
-
- Dropped nameless native `toolCall` events so they no longer appear as surfaced tool calls in owned-mode streams
|
|
2370
|
-
- Fixed truncated Gemini and Gemma tool blocks from being emitted as plain text during streaming
|
|
2371
|
-
- Fixed Gemini/Gemma in-band tool-call parsing around Python comments, raw/unicode string literals, and Gemma close-token text inside string values.
|
|
2372
|
-
|
|
2373
|
-
Older entries are archived in [packages/ai/CHANGELOG.md@a20cc0d04b64](https://github.com/can1357/oh-my-pi/blob/a20cc0d04b64f4a68fcc559cbd258743d984c50e/packages/ai/CHANGELOG.md).
|
|
2351
|
+
Older entries are archived in [packages/ai/CHANGELOG.md@d22e34333695](https://github.com/can1357/oh-my-pi/blob/d22e34333695d3dd9de7741ff45903681dba4606/packages/ai/CHANGELOG.md).
|
|
@@ -79,14 +79,15 @@ export declare class SessionAffinity implements SessionsApi {
|
|
|
79
79
|
restoredAtMs?: number;
|
|
80
80
|
}): boolean;
|
|
81
81
|
/**
|
|
82
|
-
* Copy
|
|
82
|
+
* Copy stored credential affinities from one live session to another.
|
|
83
83
|
*
|
|
84
84
|
* The target receives its own sticky entries, so request resolution, usage
|
|
85
85
|
* blocking, credential rotation, metadata, and persisted pins all continue
|
|
86
86
|
* through the target session id without retaining a live dependency on the
|
|
87
|
-
* source session.
|
|
87
|
+
* source session. `include` limits the copy to the providers it accepts,
|
|
88
|
+
* given whether the source's affinity is an explicit user pin.
|
|
88
89
|
*/
|
|
89
|
-
inherit(sourceSessionId: string, targetSessionId: string): number;
|
|
90
|
+
inherit(sourceSessionId: string, targetSessionId: string, include?: (provider: string, explicit: boolean) => boolean): number;
|
|
90
91
|
/**
|
|
91
92
|
* Release a session's sticky credential so its next `KeyCascade.get` call
|
|
92
93
|
* re-runs native pool ranking. This never blocks or penalizes the released
|
|
@@ -67,9 +67,10 @@ export declare class OAuthAccounts implements OAuthApi {
|
|
|
67
67
|
* Resolve one stored OAuth credential by its durable storage row id.
|
|
68
68
|
*
|
|
69
69
|
* Unlike the normal session resolver, this method never ranks, rotates, or
|
|
70
|
-
* falls back to sibling credentials. A forced refresh re-mints only the
|
|
71
|
-
* requested row
|
|
72
|
-
*
|
|
70
|
+
* falls back to sibling credentials. A generic forced refresh re-mints only the
|
|
71
|
+
* requested row; with `refreshReason: "auth-recovery"` it may instead reuse a
|
|
72
|
+
* still-usable recent mint of that row. Either way exact-account affinity holds
|
|
73
|
+
* for operations whose provenance and policy boundary are tied to one workspace.
|
|
73
74
|
*
|
|
74
75
|
* Returns `undefined` when the row does not exist for `provider` or an
|
|
75
76
|
* explicit runtime/config API-key override suppresses OAuth.
|
|
@@ -32,6 +32,7 @@ export interface OAuthRefresherDeps {
|
|
|
32
32
|
pool: CredentialPool;
|
|
33
33
|
policies: AccountPolicies;
|
|
34
34
|
override?: AuthStorageOptions["refreshOAuthCredential"];
|
|
35
|
+
overrideMints?: AuthStorageOptions["refreshOAuthCredentialMints"];
|
|
35
36
|
}
|
|
36
37
|
/** Single-flighted, lease-guarded OAuth refresh with compare-and-set persistence. */
|
|
37
38
|
export declare class OAuthRefresher {
|
|
@@ -8,7 +8,7 @@ import { type CredentialPool } from "./pool.js";
|
|
|
8
8
|
import { type ApiKeySelection, type OAuthSelection } from "./rank.js";
|
|
9
9
|
import { type OAuthRefresher } from "./refresh.js";
|
|
10
10
|
import type { AuthCredentialStore } from "./store.js";
|
|
11
|
-
import type
|
|
11
|
+
import { type ApiKeyCredential, type AuthApiKeyOptions, type AuthCredential, type OAuthCredential } from "./types.js";
|
|
12
12
|
import type { UsageService } from "./usage.js";
|
|
13
13
|
/** Temporary block after a transient OAuth refresh failure. */
|
|
14
14
|
export declare const OAUTH_REFRESH_FAILURE_BACKOFF_MS: number;
|
|
@@ -122,8 +122,8 @@ export interface UsageLedgerStore {
|
|
|
122
122
|
}
|
|
123
123
|
/** Broker-delegated OAuth refresh and usage-report operations. */
|
|
124
124
|
export interface CredentialUpstream {
|
|
125
|
-
/** Optional hook to notify the underlying store that usage
|
|
126
|
-
invalidateUsageCache(signal?: AbortSignal): Promise<void>;
|
|
125
|
+
/** Optional hook to notify the underlying store that usage reports (one provider's, when given) are stale. */
|
|
126
|
+
invalidateUsageCache(provider?: string, signal?: AbortSignal): Promise<void>;
|
|
127
127
|
/**
|
|
128
128
|
* Optional store-supplied OAuth refresh. When present, `AuthStorage` uses
|
|
129
129
|
* it before the per-provider local refresh path. `RemoteAuthCredentialStore`
|
|
@@ -316,6 +316,12 @@ export type AuthStorageOptions = {
|
|
|
316
316
|
* the actual refresh token never leaves the broker.
|
|
317
317
|
*/
|
|
318
318
|
refreshOAuthCredential?: (provider: Provider, credentialId: number, credential: OAuthCredential, signal?: AbortSignal, reason?: OAuthRefreshReason) => Promise<OAuthCredentials>;
|
|
319
|
+
/**
|
|
320
|
+
* Set when {@link refreshOAuthCredential} exchanges the refresh token with the
|
|
321
|
+
* provider itself instead of delegating (e.g. to a broker), so its tokens count
|
|
322
|
+
* as this process's mints for auth-recovery reuse.
|
|
323
|
+
*/
|
|
324
|
+
refreshOAuthCredentialMints?: boolean;
|
|
319
325
|
/**
|
|
320
326
|
* Human-readable description of the credential store backing this
|
|
321
327
|
* AuthStorage instance. Surfaced through {@link AuthStorage.keys.describe}
|
|
@@ -409,7 +415,7 @@ export interface ModelUsageHealthOptions {
|
|
|
409
415
|
export type AuthApiKeyOptions = {
|
|
410
416
|
baseUrl?: string;
|
|
411
417
|
modelId?: string;
|
|
412
|
-
/**
|
|
418
|
+
/** {@link oauthAccountKey}s of accounts known to serve `modelId` from multi-account discovery; OAuth selection prefers them and tries other accounts only as a last resort. */
|
|
413
419
|
accountIds?: readonly string[];
|
|
414
420
|
/**
|
|
415
421
|
* Caller's cancel signal. Threaded into any broker-bound OAuth refresh so
|
|
@@ -429,6 +435,19 @@ export type AuthApiKeyOptions = {
|
|
|
429
435
|
/** When false, select as `sessionId` would without recording the choice as that session's sticky credential. */
|
|
430
436
|
recordAffinity?: boolean;
|
|
431
437
|
};
|
|
438
|
+
/**
|
|
439
|
+
* Identity that multi-account discovery records in `Model.accountAccess` and
|
|
440
|
+
* that {@link AuthApiKeyOptions.accountIds} selection matches: the provider
|
|
441
|
+
* account id (Codex `chatgpt_account_id`), else the login email for providers
|
|
442
|
+
* whose credentials carry no account id (Antigravity), else the project id for
|
|
443
|
+
* a login whose email lookup failed. Same precedence as the credential store's
|
|
444
|
+
* identity key.
|
|
445
|
+
*/
|
|
446
|
+
export declare function oauthAccountKey(identity: {
|
|
447
|
+
accountId?: string;
|
|
448
|
+
email?: string;
|
|
449
|
+
projectId?: string;
|
|
450
|
+
}): string | undefined;
|
|
432
451
|
/** Non-secret identity bound to the OAuth credential selected for one request attempt. */
|
|
433
452
|
export interface OAuthRequestIdentity {
|
|
434
453
|
orgId?: string;
|
|
@@ -917,7 +936,7 @@ export interface KeysApi {
|
|
|
917
936
|
modelId?: string;
|
|
918
937
|
}): ApiKeyResolver;
|
|
919
938
|
}
|
|
920
|
-
/** Controls whether a row-id refresh may reuse a
|
|
939
|
+
/** Controls whether a row-id refresh may reuse a recently minted token. */
|
|
921
940
|
export interface OAuthRefreshByIdOptions {
|
|
922
941
|
/**
|
|
923
942
|
* Return the stored credential when it still holds a fresh access token this
|
|
@@ -925,6 +944,12 @@ export interface OAuthRefreshByIdOptions {
|
|
|
925
944
|
* refresh tokens repeatedly when a provider rejects every valid bearer.
|
|
926
945
|
*/
|
|
927
946
|
reuseRecentMint?: boolean;
|
|
947
|
+
/**
|
|
948
|
+
* Provider-401 recovery intent. Forwarded to a delegated refresh (an auth
|
|
949
|
+
* broker), which may then return a token it minted in the last five minutes.
|
|
950
|
+
* Local reuse still requires {@link reuseRecentMint}.
|
|
951
|
+
*/
|
|
952
|
+
reason?: OAuthRefreshReason;
|
|
928
953
|
}
|
|
929
954
|
/** OAuth login, access, account identity, and refresh operations. */
|
|
930
955
|
export interface OAuthApi {
|
|
@@ -966,9 +991,10 @@ export interface OAuthApi {
|
|
|
966
991
|
* Resolve one stored OAuth credential by its durable storage row id.
|
|
967
992
|
*
|
|
968
993
|
* Unlike the normal session resolver, this method never ranks, rotates, or
|
|
969
|
-
* falls back to sibling credentials. A forced refresh re-mints only the
|
|
970
|
-
* requested row
|
|
971
|
-
*
|
|
994
|
+
* falls back to sibling credentials. A generic forced refresh re-mints only the
|
|
995
|
+
* requested row; with `refreshReason: "auth-recovery"` it may instead reuse a
|
|
996
|
+
* still-usable recent mint of that row. Either way exact-account affinity holds
|
|
997
|
+
* for operations whose provenance and policy boundary are tied to one workspace.
|
|
972
998
|
*
|
|
973
999
|
* Returns `undefined` when the row does not exist for `provider` or an
|
|
974
1000
|
* explicit runtime/config API-key override suppresses OAuth.
|
|
@@ -1002,8 +1028,9 @@ export interface OAuthApi {
|
|
|
1002
1028
|
* Refresh the OAuth credential with the given id through a per-credential
|
|
1003
1029
|
* single-flight. Concurrent callers for the same row await the same upstream
|
|
1004
1030
|
* refresh attempt, which is required for providers that rotate refresh tokens
|
|
1005
|
-
* on every successful refresh. Mints
|
|
1006
|
-
*
|
|
1031
|
+
* on every successful refresh. Mints unless {@link OAuthRefreshByIdOptions.reuseRecentMint}
|
|
1032
|
+
* finds a recent local mint, or a delegated refresh given
|
|
1033
|
+
* {@link OAuthRefreshByIdOptions.reason} `"auth-recovery"` returns its recent mint.
|
|
1007
1034
|
*/
|
|
1008
1035
|
refresh(id: number, signal?: AbortSignal, options?: OAuthRefreshByIdOptions): Promise<AuthCredentialSnapshotEntry>;
|
|
1009
1036
|
/**
|
|
@@ -1035,14 +1062,15 @@ export interface SessionsApi {
|
|
|
1035
1062
|
restoredAtMs?: number;
|
|
1036
1063
|
}): boolean;
|
|
1037
1064
|
/**
|
|
1038
|
-
* Copy
|
|
1065
|
+
* Copy stored credential affinities from one live session to another.
|
|
1039
1066
|
*
|
|
1040
1067
|
* The target receives its own sticky entries, so request resolution, usage
|
|
1041
1068
|
* blocking, credential rotation, metadata, and persisted pins all continue
|
|
1042
1069
|
* through the target session id without retaining a live dependency on the
|
|
1043
|
-
* source session.
|
|
1070
|
+
* source session. `include` limits the copy to the providers it accepts,
|
|
1071
|
+
* given whether the source's affinity is an explicit user pin.
|
|
1044
1072
|
*/
|
|
1045
|
-
inherit(sourceSessionId: string, targetSessionId: string): number;
|
|
1073
|
+
inherit(sourceSessionId: string, targetSessionId: string, include?: (provider: string, explicit: boolean) => boolean): number;
|
|
1046
1074
|
/**
|
|
1047
1075
|
* Restrict one session's credentials for `provider` to the OAuth accounts
|
|
1048
1076
|
* whose identity key (`email:<address>|org:<id>` for org-scoped providers;
|
|
@@ -85,7 +85,7 @@ export declare class AuthBrokerClient {
|
|
|
85
85
|
fetchClientUsageSummary(query?: {
|
|
86
86
|
sinceMs?: number;
|
|
87
87
|
}, signal?: AbortSignal): Promise<ClientUsageSummaryResponse>;
|
|
88
|
-
notifyUsageStale(signal?: AbortSignal): Promise<UsageStaleResponse>;
|
|
88
|
+
notifyUsageStale(provider?: string, signal?: AbortSignal): Promise<UsageStaleResponse>;
|
|
89
89
|
refreshCredential(id: number, signal?: AbortSignal, reason?: OAuthRefreshReason): Promise<CredentialRefreshResponse>;
|
|
90
90
|
disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
|
|
91
91
|
/**
|
|
@@ -122,7 +122,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
122
122
|
/** Drop all cache rows whose keys start with the supplied prefix. */
|
|
123
123
|
deleteCachePrefix(prefix: string): void;
|
|
124
124
|
cleanExpiredCache(): void;
|
|
125
|
-
invalidateUsageCache(signal?: AbortSignal): Promise<void>;
|
|
125
|
+
invalidateUsageCache(provider?: string, signal?: AbortSignal): Promise<void>;
|
|
126
126
|
/**
|
|
127
127
|
* Store-level hook consumed by `AuthStorage` — routes refresh through the
|
|
128
128
|
* broker so the actual refresh token never leaves the broker host. Returns
|
|
@@ -149,9 +149,10 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
149
149
|
ingestUsageReport(provider: Provider, credential: OAuthCredential, report: UsageReport): boolean;
|
|
150
150
|
/**
|
|
151
151
|
* Fold locally observed request usage into the pending report and schedule
|
|
152
|
-
* a flush. One `POST /v1/usage/observed` at most per flush interval
|
|
153
|
-
*
|
|
154
|
-
* (pre-endpoint broker) disables
|
|
152
|
+
* a flush. One `POST /v1/usage/observed` at most per flush interval, plus a
|
|
153
|
+
* final one when the process exits; on failure the batch is retained and
|
|
154
|
+
* retried with the next flush. A 404 (pre-endpoint broker) disables
|
|
155
|
+
* reporting for the life of this store.
|
|
155
156
|
*
|
|
156
157
|
* `client` overrides the reporting identity — the auth-gateway attributes
|
|
157
158
|
* each request to the originating install/app instead of the gateway host.
|
|
@@ -17,6 +17,8 @@ export interface AuthBrokerServerOptions {
|
|
|
17
17
|
bind?: string;
|
|
18
18
|
/** Accept any of these bearer tokens. Empty disables auth (loopback only). */
|
|
19
19
|
bearerTokens: string[];
|
|
20
|
+
/** Honor forwarded peer headers only when the connecting proxy is trusted. Default false. */
|
|
21
|
+
trustProxyHeaders?: boolean;
|
|
20
22
|
/** Broker version string surfaced on `/v1/healthz`. */
|
|
21
23
|
version?: string;
|
|
22
24
|
/** Refresh credentials expiring within this window. Default 5 min. */
|
|
@@ -17,6 +17,8 @@ export interface AuthGatewayRouteOptions {
|
|
|
17
17
|
resolveModel: ModelResolver;
|
|
18
18
|
/** Optional supplier for `/v1/models` listing. Returns the full model array. */
|
|
19
19
|
listModels?: () => Iterable<Model<Api>>;
|
|
20
|
+
/** Providers the host does not serve; `/v1/usage` and `/v1/credentials/check` leave their accounts out. */
|
|
21
|
+
excludeProviders?: ReadonlySet<string>;
|
|
20
22
|
/** Upstream transport for every provider call; defaults to global `fetch`. Test seam. */
|
|
21
23
|
fetch?: FetchImpl;
|
|
22
24
|
}
|
|
@@ -16,8 +16,6 @@ export declare function gatewayResponseHeaders(model: Model<Api>, info: {
|
|
|
16
16
|
costUsd?: number;
|
|
17
17
|
startedAt?: number;
|
|
18
18
|
}): Record<string, string>;
|
|
19
|
-
/** Use the socket peer unless the gateway explicitly trusts its reverse proxy. */
|
|
20
|
-
export declare function resolvePeer(req: Request, socketAddress: string, trustProxyHeaders?: boolean): string;
|
|
21
19
|
/** Keep configured gateway credentials out of URL and forwarded/logged request fields. */
|
|
22
20
|
export declare function hasMisplacedBearer(req: Request, url: URL, tokens: ReadonlySet<string>): boolean;
|
|
23
21
|
/**
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
* siblings are endpoint-scoped and stay: `strictToolsDisabled`
|
|
19
19
|
* (grammar-too-large 400 for the model's tool schema),
|
|
20
20
|
* `replayUnsignedThinkingDisabled` / `thinkingReplayDisabled` (the endpoint
|
|
21
|
-
* is a signing proxy), `prefixDroppedThinkingBlocks` (blocks
|
|
22
|
-
*
|
|
21
|
+
* is a signing proxy), `prefixDroppedThinkingBlocks` (blocks a prefix-binding
|
|
22
|
+
* 400 made the client strip).
|
|
23
23
|
* - **OpenAI Responses** — the `previous_response_id` chain baselines are
|
|
24
24
|
* account-scoped: a stored response belongs to the account that created it.
|
|
25
25
|
* Strict-tools / reasoning-effort fallbacks, replay warmup and the chaining
|
|
@@ -169,6 +169,12 @@ export type AnthropicClientOptionsResult = {
|
|
|
169
169
|
* omitted there.
|
|
170
170
|
*/
|
|
171
171
|
export declare function resolveAnthropicCustomHeadersForBaseUrl(baseUrl: string | undefined): Record<string, string> | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Merge headers case-insensitively; later sources win and preserve their key casing.
|
|
174
|
+
* Undefined sources are ignored. Differently cased duplicates are removed so the
|
|
175
|
+
* Headers constructor cannot join competing values comma-separated on the wire.
|
|
176
|
+
*/
|
|
177
|
+
export declare function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]): Record<string, string>;
|
|
172
178
|
export type AnthropicUsageLike = {
|
|
173
179
|
cache_creation?: {
|
|
174
180
|
ephemeral_5m_input_tokens?: number | null;
|
|
@@ -169,13 +169,13 @@ export declare function emptyGrepPatternRejection(pattern: string | undefined, g
|
|
|
169
169
|
* so reparsing unconditionally would erase the arguments of every such block
|
|
170
170
|
* caught open by a truncated stream.
|
|
171
171
|
*
|
|
172
|
-
* Server-owned blocks are also paired here. `connect-scm
|
|
173
|
-
* stamped {@link kCursorExecResolved} the moment they open,
|
|
174
|
-
* synthesizes no placeholder for them and only their
|
|
175
|
-
* pairs a result. A transport that closes before
|
|
176
|
-
* call unpaired, and `buildSessionContext` strips
|
|
177
|
-
* rebuilt transcript — the interaction disappears.
|
|
178
|
-
* emitted instead.
|
|
172
|
+
* Server-owned blocks are also paired here. `connect-scm`, `todo` and
|
|
173
|
+
* `web-fetch` are stamped {@link kCursorExecResolved} the moment they open,
|
|
174
|
+
* so `agent-loop.ts` synthesizes no placeholder for them and only their
|
|
175
|
+
* `toolCallCompleted` frame pairs a result. A transport that closes before
|
|
176
|
+
* that frame would leave the call unpaired, and `buildSessionContext` strips
|
|
177
|
+
* a dangling call from every rebuilt transcript — the interaction disappears.
|
|
178
|
+
* An interrupted result is emitted instead.
|
|
179
179
|
*
|
|
180
180
|
* MCP blocks are excluded even when resolved: the exec dispatch that marked
|
|
181
181
|
* them owns their result, and `drainInFlightDispatches` awaits it before this
|
|
@@ -13,7 +13,7 @@ export declare function pollCursorAuth(uuid: string, verifier: string): Promise<
|
|
|
13
13
|
}>;
|
|
14
14
|
export declare function loginCursor(onAuthUrl: (url: string) => void, onPollStart?: () => void): Promise<OAuthCredentials>;
|
|
15
15
|
export declare function loginCursorHook(callbacks: OAuthController): Promise<OAuthCredentials>;
|
|
16
|
-
export declare function refreshCursorToken(
|
|
16
|
+
export declare function refreshCursorToken(refreshToken: string): Promise<OAuthCredentials>;
|
|
17
17
|
export declare function refreshCursorHook(credentials: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
18
18
|
/** Request headers that present `accessToken` to cursor.com as its user's web session. */
|
|
19
19
|
export declare function cursorSessionHeaders(userId: string, accessToken: string): Record<string, string>;
|
package/dist/types/usage.d.ts
CHANGED
|
@@ -142,6 +142,8 @@ export interface UsageReport {
|
|
|
142
142
|
* explicit fraction > used/limit > percent-unit used > inverted remaining.
|
|
143
143
|
*/
|
|
144
144
|
export declare function resolveUsedFraction(limit: UsageLimit): number | undefined;
|
|
145
|
+
/** Whether remaining quota is at or below the reserve (both 0..1 fractions). */
|
|
146
|
+
export declare function isWithinUsageReserve(remainingFraction: number, reserveFraction: number): boolean;
|
|
145
147
|
/**
|
|
146
148
|
* One recorded usage-limit snapshot: a single limit window of one account at
|
|
147
149
|
* a point in time. The usage cache itself is latest-snapshot-only; history
|
|
@@ -9,6 +9,11 @@ export declare function resolveAnthropicBaseUrlFromEnv(): string | undefined;
|
|
|
9
9
|
* Checks if a token is an OAuth token by looking for sk-ant-oat prefix.
|
|
10
10
|
*/
|
|
11
11
|
export declare function isOAuthToken(apiKey: string): boolean;
|
|
12
|
+
/** Optional request-shaping overrides for an already-resolved Anthropic credential. */
|
|
13
|
+
export interface AnthropicAuthOptions {
|
|
14
|
+
/** Override token detection; false explicitly disables OAuth-style request shaping. */
|
|
15
|
+
isOAuth?: boolean;
|
|
16
|
+
}
|
|
12
17
|
/**
|
|
13
18
|
* Build an {@link AnthropicAuthConfig} from an already-resolved API key.
|
|
14
19
|
*
|
|
@@ -17,18 +22,27 @@ export declare function isOAuthToken(apiKey: string): boolean;
|
|
|
17
22
|
* env-derived base; pass `undefined` to fall back to FOUNDRY/ANTHROPIC env
|
|
18
23
|
* resolution and finally `DEFAULT_BASE_URL`.
|
|
19
24
|
*
|
|
20
|
-
* `isOAuth`
|
|
21
|
-
*
|
|
25
|
+
* `options.isOAuth` defaults to token detection. Pass the selected model's
|
|
26
|
+
* `isOAuth` to match the streaming path, which lets the model override the
|
|
27
|
+
* prefix (custom `anthropic-messages` providers default to OAuth shaping).
|
|
22
28
|
*/
|
|
23
|
-
export declare function buildAnthropicAuthConfig(apiKey: string, baseUrl?: string): AnthropicAuthConfig;
|
|
29
|
+
export declare function buildAnthropicAuthConfig(apiKey: string, baseUrl?: string, options?: AnthropicAuthOptions): AnthropicAuthConfig;
|
|
30
|
+
/** Configured headers and fingerprint override policy for Anthropic hosted web search. */
|
|
31
|
+
export interface AnthropicSearchHeaderOptions {
|
|
32
|
+
/** The selected model's configured headers (`ModelRegistry.resolveModelHeaders`). */
|
|
33
|
+
modelHeaders?: Record<string, string>;
|
|
34
|
+
/** The selected model's `compat.allowAnthropicHeaderOverrides`. */
|
|
35
|
+
allowAnthropicHeaderOverrides?: boolean;
|
|
36
|
+
}
|
|
24
37
|
/**
|
|
25
38
|
* Builds HTTP headers for Anthropic API requests (search variant).
|
|
26
39
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* gateway)
|
|
40
|
+
* Model headers and `ANTHROPIC_CUSTOM_HEADERS` (when the resolver deems them
|
|
41
|
+
* applicable: Foundry mode, or a non-Anthropic base URL such as an enterprise
|
|
42
|
+
* gateway) go through the same case-insensitive merge and enforced-header
|
|
43
|
+
* filtering as the streaming path, so web search behaves identically.
|
|
30
44
|
*/
|
|
31
|
-
export declare function buildAnthropicSearchHeaders(auth: AnthropicAuthConfig): Record<string, string>;
|
|
45
|
+
export declare function buildAnthropicSearchHeaders(auth: AnthropicAuthConfig, options?: AnthropicSearchHeaderOptions): Record<string, string>;
|
|
32
46
|
/**
|
|
33
47
|
* Builds the full API URL for Anthropic messages endpoint.
|
|
34
48
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.8.
|
|
3
|
+
"version": "18.8.5",
|
|
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.8.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.8.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.8.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.8.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.8.
|
|
158
|
+
"@oh-my-pi/omptype": "18.8.5",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.8.5",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.8.5",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.8.5",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.8.5"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
package/src/auth/affinity.ts
CHANGED
|
@@ -360,19 +360,25 @@ export class SessionAffinity implements SessionsApi {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
/**
|
|
363
|
-
* Copy
|
|
363
|
+
* Copy stored credential affinities from one live session to another.
|
|
364
364
|
*
|
|
365
365
|
* The target receives its own sticky entries, so request resolution, usage
|
|
366
366
|
* blocking, credential rotation, metadata, and persisted pins all continue
|
|
367
367
|
* through the target session id without retaining a live dependency on the
|
|
368
|
-
* source session.
|
|
368
|
+
* source session. `include` limits the copy to the providers it accepts,
|
|
369
|
+
* given whether the source's affinity is an explicit user pin.
|
|
369
370
|
*/
|
|
370
|
-
inherit(
|
|
371
|
+
inherit(
|
|
372
|
+
sourceSessionId: string,
|
|
373
|
+
targetSessionId: string,
|
|
374
|
+
include?: (provider: string, explicit: boolean) => boolean,
|
|
375
|
+
): number {
|
|
371
376
|
if (!sourceSessionId || !targetSessionId || sourceSessionId === targetSessionId) return 0;
|
|
372
377
|
let inherited = 0;
|
|
373
378
|
for (const provider of this.#pool.providers()) {
|
|
374
379
|
const credential = this.get(provider, sourceSessionId);
|
|
375
380
|
if (!credential || !this.#permits(provider, targetSessionId, credential.index)) continue;
|
|
381
|
+
if (include && !include(provider, credential.explicit === true)) continue;
|
|
376
382
|
this.record(
|
|
377
383
|
provider,
|
|
378
384
|
targetSessionId,
|
package/src/auth/health.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Provider } from "../types";
|
|
2
|
-
import { resolveUsedFraction } from "../usage";
|
|
2
|
+
import { isWithinUsageReserve, resolveUsedFraction } from "../usage";
|
|
3
3
|
import type {
|
|
4
4
|
CredentialRankingContext,
|
|
5
5
|
UsageCredential,
|
|
@@ -224,7 +224,7 @@ export class CredentialHealth implements HealthApi {
|
|
|
224
224
|
return {
|
|
225
225
|
credentialId: entry.id,
|
|
226
226
|
credentialType,
|
|
227
|
-
state: remainingFraction
|
|
227
|
+
state: isWithinUsageReserve(remainingFraction, resolveReserveFraction(entry)) ? "reserve" : "healthy",
|
|
228
228
|
remainingFraction,
|
|
229
229
|
};
|
|
230
230
|
}),
|
package/src/auth/oauth.ts
CHANGED
|
@@ -179,9 +179,20 @@ export class OAuthAccounts implements OAuthApi {
|
|
|
179
179
|
options: AuthApiKeyOptions | undefined,
|
|
180
180
|
): Promise<OAuthAccessResolution> {
|
|
181
181
|
try {
|
|
182
|
+
// tryOAuth refreshes only expired tokens; it resyncs this row from the store after a forced re-mint.
|
|
183
|
+
let index = selection.index;
|
|
184
|
+
if (options?.forceRefresh) {
|
|
185
|
+
await this.refresh(selection.credentialId, options.signal, {
|
|
186
|
+
reuseRecentMint: options.refreshReason === "auth-recovery",
|
|
187
|
+
reason: options.refreshReason,
|
|
188
|
+
});
|
|
189
|
+
// The refresh re-lists rows, so a concurrently removed row can shift this one's position.
|
|
190
|
+
index = this.#deps.pool.entries(provider).findIndex(entry => entry.id === selection.credentialId);
|
|
191
|
+
if (index === -1) throw new Error(`OAuth credential ${selection.credentialId} was removed during refresh`);
|
|
192
|
+
}
|
|
182
193
|
const resolved = await this.#deps.selector.tryOAuth(
|
|
183
194
|
provider,
|
|
184
|
-
{ credential: selection.credential, index
|
|
195
|
+
{ credential: selection.credential, index },
|
|
185
196
|
providerKey,
|
|
186
197
|
undefined,
|
|
187
198
|
options,
|
|
@@ -289,9 +300,10 @@ export class OAuthAccounts implements OAuthApi {
|
|
|
289
300
|
* Resolve one stored OAuth credential by its durable storage row id.
|
|
290
301
|
*
|
|
291
302
|
* Unlike the normal session resolver, this method never ranks, rotates, or
|
|
292
|
-
* falls back to sibling credentials. A forced refresh re-mints only the
|
|
293
|
-
* requested row
|
|
294
|
-
*
|
|
303
|
+
* falls back to sibling credentials. A generic forced refresh re-mints only the
|
|
304
|
+
* requested row; with `refreshReason: "auth-recovery"` it may instead reuse a
|
|
305
|
+
* still-usable recent mint of that row. Either way exact-account affinity holds
|
|
306
|
+
* for operations whose provenance and policy boundary are tied to one workspace.
|
|
295
307
|
*
|
|
296
308
|
* Returns `undefined` when the row does not exist for `provider` or an
|
|
297
309
|
* explicit runtime/config API-key override suppresses OAuth.
|