@oh-my-pi/pi-ai 18.4.4 → 18.4.6

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +23 -26
  2. package/dist/types/auth/cascade.d.ts +17 -6
  3. package/dist/types/auth/rank.d.ts +7 -1
  4. package/dist/types/auth/refresh.d.ts +8 -0
  5. package/dist/types/auth/types.d.ts +19 -2
  6. package/dist/types/auth-gateway/dispatch.d.ts +7 -7
  7. package/dist/types/auth-gateway/http.d.ts +4 -1
  8. package/dist/types/auth-gateway/types.d.ts +2 -0
  9. package/dist/types/auth-retry.d.ts +3 -1
  10. package/dist/types/index.d.ts +1 -0
  11. package/dist/types/providers/anthropic.d.ts +7 -0
  12. package/dist/types/providers/cursor/interaction-query.d.ts +4 -2
  13. package/dist/types/providers/cursor.d.ts +14 -12
  14. package/dist/types/providers/factory-droid/gemini.d.ts +15 -0
  15. package/dist/types/providers/factory-droid.d.ts +13 -0
  16. package/dist/types/providers/google-shared.d.ts +12 -0
  17. package/dist/types/providers/openai-chat-wire.d.ts +7 -2
  18. package/dist/types/providers/openai-completions.d.ts +1 -1
  19. package/dist/types/registry/oauth/device-code.d.ts +2 -2
  20. package/dist/types/registry/oauth/factory-droid.d.ts +3 -0
  21. package/dist/types/registry/oauth/types.d.ts +13 -3
  22. package/dist/types/types.d.ts +5 -0
  23. package/dist/types/usage/factory-droid.d.ts +11 -0
  24. package/dist/types/usage.d.ts +4 -0
  25. package/dist/types/utils/schema/allowlist.d.ts +2 -0
  26. package/dist/types/utils/schema/index.d.ts +1 -0
  27. package/package.json +6 -6
  28. package/src/api-registry.ts +1 -0
  29. package/src/auth/cascade.ts +51 -11
  30. package/src/auth/health.ts +3 -0
  31. package/src/auth/oauth.ts +4 -0
  32. package/src/auth/rank.ts +11 -1
  33. package/src/auth/refresh.ts +27 -2
  34. package/src/auth/select.ts +52 -24
  35. package/src/auth/types.ts +18 -2
  36. package/src/auth/usage-cache.ts +6 -0
  37. package/src/auth/usage.ts +10 -5
  38. package/src/auth-gateway/dispatch.ts +20 -20
  39. package/src/auth-gateway/http.ts +44 -2
  40. package/src/auth-gateway/routes/embeddings.ts +1 -1
  41. package/src/auth-gateway/routes/images.ts +1 -1
  42. package/src/auth-gateway/routes/rerank.ts +1 -1
  43. package/src/auth-gateway/routes/speech.ts +1 -1
  44. package/src/auth-gateway/routes/systemone.ts +1 -1
  45. package/src/auth-gateway/routes/transcriptions.ts +1 -1
  46. package/src/auth-gateway/routes/video.ts +4 -3
  47. package/src/auth-gateway/server.ts +40 -10
  48. package/src/auth-gateway/types.ts +2 -0
  49. package/src/auth-retry.ts +3 -1
  50. package/src/auth-storage.ts +3 -1
  51. package/src/index.ts +1 -0
  52. package/src/providers/amazon-bedrock.ts +10 -8
  53. package/src/providers/anthropic.ts +126 -16
  54. package/src/providers/cursor/interaction-query.ts +11 -8
  55. package/src/providers/cursor.ts +980 -314
  56. package/src/providers/factory-droid/droid-identity.md +1 -0
  57. package/src/providers/factory-droid/gemini.ts +538 -0
  58. package/src/providers/factory-droid.ts +623 -0
  59. package/src/providers/google-shared.ts +2 -2
  60. package/src/providers/openai-chat-wire.ts +11 -2
  61. package/src/providers/openai-completions.ts +106 -13
  62. package/src/registry/engine/device-code.ts +2 -2
  63. package/src/registry/engine/refresh.ts +1 -0
  64. package/src/registry/hooks/device-code.ts +1 -0
  65. package/src/registry/oauth/device-code.ts +19 -3
  66. package/src/registry/oauth/factory-droid.ts +62 -0
  67. package/src/registry/oauth/types.ts +13 -3
  68. package/src/stream.ts +38 -4
  69. package/src/types.ts +5 -0
  70. package/src/usage/cursor.ts +3 -4
  71. package/src/usage/factory-droid.ts +226 -0
  72. package/src/usage/registry.ts +3 -0
  73. package/src/usage/xai-oauth.ts +21 -8
  74. package/src/usage.ts +4 -0
  75. package/src/utils/schema/CONSTRAINTS.md +15 -0
  76. package/src/utils/schema/allowlist.ts +160 -0
  77. package/src/utils/schema/index.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.4.6] - 2026-10-01
6
+
7
+ ### Fixed
8
+
9
+ - Fixed forced tool calls failing for Claude Opus 5.5 and Sonnet 5.5 through Amazon Bedrock, including required-tool retries in plan mode.
10
+
11
+ ## [18.4.5] - 2026-09-30
12
+
13
+ ### Added
14
+
15
+ - Added Factory Droid OAuth, HTTP streaming across Anthropic, OpenAI and Gemini protocols, and pool-aware usage reporting ([#8577](https://github.com/can1357/oh-my-pi/pull/8577) by [@will-bogusz](https://github.com/will-bogusz), continued in [#13276](https://github.com/can1357/oh-my-pi/pull/13276) by [@DusKing1](https://github.com/DusKing1)).
16
+ - `AuthStorage.keys.setConfig(provider, value, { fallback: true })` registers a key that is used only when no stored OAuth or `/login` credential exists, instead of overriding them; `removeConfig`/`clearConfig` also clear these fallbacks ([#13815](https://github.com/can1357/oh-my-pi/pull/13815) by [@H4vC](https://github.com/H4vC))
17
+ - Cursor turns now fall back to Cursor's HTTP/1 streaming transport when HTTP/2 is unavailable, surface Cursor's structured service errors, and resume from the last safe server checkpoint after a dropped stream ([#11613](https://github.com/can1357/oh-my-pi/pull/11613) by [@will-bogusz](https://github.com/will-bogusz)).
18
+
19
+ ### Fixed
20
+
21
+ - Auth gateway checks for configured bearer tokens in URLs or forwarded/logged headers only after authentication; unauthorized requests use socket peers and redact unknown paths. Authenticated requests with misplaced tokens are rejected before provider dispatch ([#13827](https://github.com/can1357/oh-my-pi/pull/13827) by [@shawnkoh](https://github.com/shawnkoh)).
22
+ - Codex sessions no longer keep spending an account's credits after its plan limit is reached while another logged-in account still has plan usage left; new and ongoing sessions switch to that account, and credits are used only when no account has plan usage left ([#13889](https://github.com/can1357/oh-my-pi/issues/13889)).
23
+ - Runtime usage providers (`usage.setProvider`, extension `registerProvider({ usage })`) now key cached reports by their own `cacheVersion`, so reports written by processes without the override are no longer served to it ([#13814](https://github.com/can1357/oh-my-pi/issues/13814)).
24
+ - xAI OAuth accounts with active weekly credits no longer switch away solely because an uncertain monthly counter exceeds its limit ([#13806](https://github.com/can1357/oh-my-pi/issues/13806)).
25
+ - Cursor retries after a rejected conversation now keep the tool calls and results already completed in the turn, instead of re-sending the last message and redoing that work ([#11613](https://github.com/can1357/oh-my-pi/pull/11613) by [@will-bogusz](https://github.com/will-bogusz)).
26
+
5
27
  ## [18.4.4] - 2026-09-29
6
28
 
7
29
  ### Added
@@ -2294,29 +2316,4 @@
2294
2316
 
2295
2317
  - Added the optional `ToolResultMessage.useless` flag: tools can declare a finished result contextually useless (zero matches, elapsed wait) so compaction passes may elide it once consumed. Never serialized to provider wire formats and never set together with `isError`.
2296
2318
 
2297
- ## [15.12.0] - 2026-06-12
2298
-
2299
- ### Fixed
2300
-
2301
- - Fixed Anthropic requests bypassing lone-surrogate sanitization after payload hooks or Anthropic-origin tool-call replay: the model itself can emit unpaired surrogate escapes in its own tool-argument JSON (streamed out fine, then rejected with `400 The request body is not valid JSON` on every subsequent request, bricking the session). The final Anthropic payload is now deep-sanitized with `toWellFormed()` immediately before SDK serialization; the pass is identity-preserving, so well-formed arguments stay byte-identical and prompt-cache prefixes are unaffected.
2302
-
2303
- ## [15.11.8] - 2026-06-12
2304
-
2305
- ### Breaking Changes
2306
-
2307
- - Removed the Codex SSE stateful transport path, so SSE turns no longer send `previous_response_id` with delta input and now always send the full transcript
2308
-
2309
- ### Changed
2310
-
2311
- - Scoped `x-codex-turn-state` handling to within-turn continuations so only tool-loop follow-ups include the turn-state header and new user turns start without it
2312
-
2313
- ### Removed
2314
-
2315
- - Removed the `statefulResponses` option from `OpenAICodexResponsesOptions`, and SSE stateful mode is no longer controlled by the `PI_CODEX_STATEFUL`-style flag
2316
-
2317
- ### Fixed
2318
-
2319
- - Fixed the platform OpenAI Responses and Codex websocket stale-chain classifiers missing the "Unsupported parameter: previous_response_id" rejection phrasing (FastAPI-style `detail` body with no `error.code`), so a chained turn now falls back to a full-transcript replay instead of surfacing the 400
2320
- - Fixed the HTTP-400 raw-request dump for Codex SSE to record the body actually sent on the wire instead of the pre-transport request body, which made chained-request failures look like the rejected parameter was never sent
2321
-
2322
- Older entries are archived in [packages\ai\CHANGELOG.md@07e9197a3012](https://github.com/can1357/oh-my-pi/blob/07e9197a3012f58c459f1faabeb324decc21f41d/packages\ai\CHANGELOG.md).
2319
+ Older entries are archived in [packages\ai\CHANGELOG.md@cd762117522c](https://github.com/can1357/oh-my-pi/blob/cd762117522cc4122cd45b8d2d14de5f7be5133b/packages\ai\CHANGELOG.md).
@@ -2,7 +2,7 @@ import { type ApiKeyResolver, type ResolvedApiKey } from "../auth-retry.js";
2
2
  import type { SessionAffinity } from "./affinity.js";
3
3
  import type { CredentialPool } from "./pool.js";
4
4
  import type { CredentialSelector } from "./select.js";
5
- import type { AuthApiKeyOptions, AuthCredential, AuthSource, AuthSourceOptions, KeysApi, LimitsApi } from "./types.js";
5
+ import type { AuthApiKeyOptions, AuthCredential, AuthSource, AuthSourceOptions, KeysApi, LimitsApi, OAuthRequestIdentity } from "./types.js";
6
6
  /** Runtime (--api-key) and config (models.yml) key overrides plus the config-value resolver. */
7
7
  export declare class KeyOverrides {
8
8
  #private;
@@ -10,6 +10,8 @@ export declare class KeyOverrides {
10
10
  has(provider: string): boolean;
11
11
  runtimeKey(provider: string): string | undefined;
12
12
  configKey(provider: string): string | undefined;
13
+ /** Config value consulted only after stored OAuth/login credentials. */
14
+ fallbackKey(provider: string): string | undefined;
13
15
  /** Resolve a config value (env var name, "!command", literal) to the secret. */
14
16
  resolve(config: string): Promise<string | undefined>;
15
17
  /**
@@ -30,10 +32,17 @@ export declare class KeyOverrides {
30
32
  *
31
33
  * Lower priority than {@link KeyOverrides.setRuntime} so a CLI `--api-key`
32
34
  * still wins for the duration of a single invocation.
35
+ *
36
+ * `fallback: true` instead ranks the value below stored OAuth and `/login`
37
+ * credentials (at the env-var tier). Providers that own a `/login` flow use
38
+ * this so their default key reference (e.g. an unset env-var name, which
39
+ * resolves to its literal text) cannot shadow the key the user logged in with.
33
40
  */
34
- setConfig(provider: string, apiKeyConfig: string): void;
41
+ setConfig(provider: string, apiKeyConfig: string, options?: {
42
+ fallback?: boolean;
43
+ }): void;
35
44
  /**
36
- * Remove a single config-sourced API key override.
45
+ * Remove a single config-sourced API key (override or fallback).
37
46
  */
38
47
  removeConfig(provider: string): void;
39
48
  /**
@@ -85,7 +94,7 @@ export declare class KeyCascade implements KeysApi {
85
94
  /**
86
95
  * Classify where a provider's auth comes from, following the same precedence
87
96
  * as {@link KeyCascade.get}: runtime override → config override →
88
- * stored OAuth → login-stored api_key → env var → stored api_key.
97
+ * stored OAuth → login-stored api_key → config fallback → env var → stored api_key.
89
98
  * Returns undefined when no auth is configured.
90
99
  *
91
100
  * Compact, structured counterpart to {@link KeyCascade.describe}; `env`
@@ -107,7 +116,7 @@ export declare class KeyCascade implements KeysApi {
107
116
  * Priority (first match wins): runtime override, config override, OAuth,
108
117
  * login API key, environment variable, then another stored API key.
109
118
  */
110
- get(provider: string, sessionId?: string, options?: AuthApiKeyOptions, onCredentialId?: (id: number) => void): Promise<string | undefined>;
119
+ get(provider: string, sessionId?: string, options?: AuthApiKeyOptions, onCredentialId?: (id: number, identity?: OAuthRequestIdentity) => void): Promise<string | undefined>;
111
120
  /**
112
121
  * Build an {@link ApiKeyResolver} backed by this storage, implementing the
113
122
  * central a/b/c auth-retry policy:
@@ -140,7 +149,9 @@ export declare class KeyCascade implements KeysApi {
140
149
  describe(provider: string, sessionId?: string): string | undefined;
141
150
  setRuntime(provider: string, apiKey: string): void;
142
151
  removeRuntime(provider: string): void;
143
- setConfig(provider: string, apiKeyConfig: string): void;
152
+ setConfig(provider: string, apiKeyConfig: string, options?: {
153
+ fallback?: boolean;
154
+ }): void;
144
155
  removeConfig(provider: string): void;
145
156
  clearConfig(): void;
146
157
  setResolver(resolver: (config: string) => Promise<string | undefined>): void;
@@ -18,6 +18,11 @@ export type UsageCandidate<T extends AuthCredential> = {
18
18
  inReserve?: boolean;
19
19
  /** True only when reserve ranking had a usable remaining-fraction measurement. */
20
20
  reserveMeasured?: boolean;
21
+ /**
22
+ * Present after ranking: the account's renewable allowance for this request is
23
+ * spent, so if it still serves it draws on paid overage such as Codex credits.
24
+ */
25
+ allowanceSpent?: boolean;
21
26
  };
22
27
  /** OAuth credential eligible for usage ranking. */
23
28
  export type OAuthCandidate = UsageCandidate<OAuthCredential>;
@@ -35,6 +40,7 @@ export type UsageRankedCandidate<T extends AuthCredential> = UsageCandidate<T> &
35
40
  reserveMeasured?: boolean;
36
41
  accountPriority: number;
37
42
  hasPriorityBoost: boolean;
43
+ allowanceSpent: boolean;
38
44
  usageMeasured: boolean;
39
45
  planPriority: number;
40
46
  secondaryUsed: number;
@@ -49,5 +55,5 @@ export type RankedOAuthCandidate = UsageRankedCandidate<OAuthCredential>;
49
55
  export type RankedApiKeyCandidate = UsageRankedCandidate<ApiKeyCredential>;
50
56
  /** Rank accounts by model-plan eligibility when a plan gate applies. */
51
57
  export declare function planPriority(gate: PlanGate | undefined, report: UsageReport | null): number;
52
- /** Sort ranked candidates by blocks, plan, reserve, boost, hot window, usage and drain. */
58
+ /** Sort ranked candidates by blocks, plan, spent allowance, reserve, boost, hot window, usage and drain. */
53
59
  export declare function orderUsageRankedCandidates<T extends AuthCredential>(candidates: UsageRankedCandidate<T>[], planGated: boolean): UsageCandidate<T>[];
@@ -17,6 +17,13 @@ import { type AuthCredentialSnapshotEntry, type AuthStorageOptions, type OAuthCr
17
17
  * the rotation cadence by <4%.
18
18
  */
19
19
  export declare const OAUTH_REFRESH_SKEW_MS = 60000;
20
+ type OrganizationScope = Pick<OAuthCredentials, "orgId" | "region" | "inferenceRegion" | "activeOrganizationId" | "orgName">;
21
+ /**
22
+ * Organization-scoped identity after a refresh: refreshed values win; stored
23
+ * org/residency/inference scope carries over only while the organization is
24
+ * unchanged. The WorkOS selection always falls back to the stored one.
25
+ */
26
+ export declare function mergeRefreshedOrganizationScope(current: OrganizationScope, refreshed: OrganizationScope): OrganizationScope;
20
27
  /** Merge provider refresh bytes with the stored OAuth row, preserving subtype metadata for every refresh path. */
21
28
  export declare function mergeRefreshedCredential<T extends OAuthCredential>(current: T, refreshed: OAuthCredentials): T;
22
29
  /** Dependencies for lease-guarded OAuth refresh. */
@@ -57,3 +64,4 @@ export declare class OAuthRefresher {
57
64
  */
58
65
  refreshById(id: number, signal?: AbortSignal, options?: OAuthRefreshByIdOptions): Promise<AuthCredentialSnapshotEntry>;
59
66
  }
67
+ export {};
@@ -180,6 +180,9 @@ export type CompletionProbeCredential = {
180
180
  email?: string;
181
181
  enterpriseUrl?: string;
182
182
  apiEndpoint?: string;
183
+ orgId?: string;
184
+ region?: string;
185
+ inferenceRegion?: "global" | "eu" | "us";
183
186
  };
184
187
  /**
185
188
  * Caller-supplied bearer probe. Receives the post-refresh credential for a
@@ -422,6 +425,12 @@ export type AuthApiKeyOptions = {
422
425
  /** Explicit provider-401 recovery; generic force refreshes leave this unset. */
423
426
  refreshReason?: OAuthRefreshReason;
424
427
  };
428
+ /** Non-secret identity bound to the OAuth credential selected for one request attempt. */
429
+ export interface OAuthRequestIdentity {
430
+ orgId?: string;
431
+ region?: string;
432
+ inferenceRegion?: "global" | "eu" | "us";
433
+ }
425
434
  /**
426
435
  * Refreshed OAuth access plus identity metadata returned by
427
436
  * {@link AuthStorage.oauth.access}. Callers that authenticate via a bearer
@@ -441,6 +450,8 @@ export interface OAuthAccess {
441
450
  /** Organization/workspace the credential is scoped to (Anthropic/ChatGPT multi-subscription). */
442
451
  orgId?: string;
443
452
  orgName?: string;
453
+ region?: string;
454
+ inferenceRegion?: "global" | "eu" | "us";
444
455
  }
445
456
  /**
446
457
  * Identity slice of the credential a successful {@link AuthStorage.oauth.login}
@@ -848,10 +859,16 @@ export interface KeysApi {
848
859
  *
849
860
  * Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
850
861
  * still wins for the duration of a single invocation.
862
+ *
863
+ * `fallback: true` ranks the value below stored OAuth and `/login`
864
+ * credentials instead, so a provider's default key reference cannot shadow
865
+ * a key the user logged in with.
851
866
  */
852
- setConfig(provider: string, apiKeyConfig: string): void;
867
+ setConfig(provider: string, apiKeyConfig: string, options?: {
868
+ fallback?: boolean;
869
+ }): void;
853
870
  /**
854
- * Remove a single config-sourced API key override.
871
+ * Remove a single config-sourced API key (override or fallback).
855
872
  */
856
873
  removeConfig(provider: string): void;
857
874
  /**
@@ -1,4 +1,4 @@
1
- import type { ApiKeyResolver } from "../auth-retry.js";
1
+ import type { ApiKeyResolver, ResolvedApiKey } from "../auth-retry.js";
2
2
  import type { AuthStorage } from "../auth-storage.js";
3
3
  import { type GatewayErrorClassification } from "../error/gateway.js";
4
4
  import type { Api, FetchImpl, Model, Usage } from "../types.js";
@@ -42,12 +42,12 @@ export declare function resolveGatewayAccount(storage: AuthStorage, provider: st
42
42
  * Resolve the credential for one request from broker-backed storage.
43
43
  *
44
44
  * pi-ai clients never consult `AuthStorage`; the gateway resolves the bearer
45
- * (an OAuth access token refreshed through the broker when needed) and hands
46
- * it to the client. Returns the key, or the error classification the route
47
- * should encode in its own envelope: storage failures map through
48
- * {@link classifyGatewayError}, a provider without any credential is a 401.
45
+ * and its OAuth identity together (refreshing through the broker when needed).
46
+ * Keep this selection snapshot intact even if another request replaces the
47
+ * stored token before dispatch. Storage failures map through
48
+ * {@link classifyGatewayError}; a provider without any credential is a 401.
49
49
  */
50
- export declare function resolveGatewayApiKey(storage: AuthStorage, model: Model<Api>, sessionId: string, signal: AbortSignal, peer: string): Promise<string | GatewayErrorClassification>;
50
+ export declare function resolveGatewayApiKey(storage: AuthStorage, model: Model<Api>, sessionId: string, signal: AbortSignal, peer: string): Promise<ResolvedApiKey | GatewayErrorClassification>;
51
51
  /**
52
52
  * Build the {@link ApiKeyResolver} handed to a pi-ai client for a gateway
53
53
  * request. Drives the central a/b/c auth-retry policy server-side:
@@ -63,7 +63,7 @@ export declare function resolveGatewayApiKey(storage: AuthStorage, model: Model<
63
63
  * routes that retain provider session state use it to re-key the account
64
64
  * lease, one-shot routes pass `undefined`.
65
65
  */
66
- export declare function buildGatewayApiKeyResolver(storage: AuthStorage, model: Model<Api>, sessionId: string, initialKey: string, requestSignal: AbortSignal, format: string, peer: string, onResolvedKey?: (apiKey: string) => void): ApiKeyResolver;
66
+ export declare function buildGatewayApiKeyResolver(storage: AuthStorage, model: Model<Api>, sessionId: string, initialKey: ResolvedApiKey, requestSignal: AbortSignal, format: string, peer: string, onResolvedKey?: (apiKey: string) => void): ApiKeyResolver;
67
67
  /**
68
68
  * Attribute one settled upstream request to the originating client via the
69
69
  * broker's observed-usage channel (`AuthStorage.usage.observe`, batched
@@ -16,7 +16,10 @@ export declare function gatewayResponseHeaders(model: Model<Api>, info: {
16
16
  costUsd?: number;
17
17
  startedAt?: number;
18
18
  }): Record<string, string>;
19
- export declare function resolvePeer(req: Request): 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
+ /** Keep configured gateway credentials out of URL and forwarded/logged request fields. */
22
+ export declare function hasMisplacedBearer(req: Request, url: URL, tokens: ReadonlySet<string>): boolean;
20
23
  /**
21
24
  * Constant-time byte comparison. Falls back to a manual XOR accumulator if
22
25
  * `node:crypto.timingSafeEqual` isn't available. Always processes every byte
@@ -125,6 +125,8 @@ export interface AuthGatewayServerOptions {
125
125
  bind?: string;
126
126
  /** Accept any of these bearer tokens. Empty allows unauthenticated calls. */
127
127
  bearerTokens: string[];
128
+ /** Honor forwarded peer headers only when the connecting proxy is trusted. Default false. */
129
+ trustProxyHeaders?: boolean;
128
130
  /** Version surfaced on `/healthz`. */
129
131
  version?: string;
130
132
  }
@@ -1,4 +1,4 @@
1
- import type { LimitsApi, OAuthAccess, OAuthApi } from "./auth/types.js";
1
+ import type { LimitsApi, OAuthAccess, OAuthApi, OAuthRequestIdentity } from "./auth/types.js";
2
2
  import { isAuthRetryableError } from "./error/auth-classify.js";
3
3
  /**
4
4
  * Context passed to an {@link ApiKeyResolver} on each resolution attempt.
@@ -42,6 +42,8 @@ export interface ResolvedApiKey {
42
42
  * already sent it before that block.
43
43
  */
44
44
  afterSiblingWait?: boolean;
45
+ /** Non-secret request scope belonging to this bearer, replaced on account rotation. */
46
+ oauthIdentity?: OAuthRequestIdentity;
45
47
  }
46
48
  export type ApiKeyResolution = string | ResolvedApiKey | undefined;
47
49
  export type ApiKeyResolver = (ctx: ApiKeyResolveContext) => Promise<ApiKeyResolution> | ApiKeyResolution;
@@ -45,6 +45,7 @@ export * from "./usage.js";
45
45
  export * from "./usage/claude.js";
46
46
  export * from "./usage/claude-reset.js";
47
47
  export * from "./usage/cursor.js";
48
+ export * from "./usage/factory-droid.js";
48
49
  export * from "./usage/gemini.js";
49
50
  export * from "./usage/github-copilot.js";
50
51
  export * from "./usage/google-antigravity.js";
@@ -234,6 +234,13 @@ export declare function budgetThinkingOutput(maxTokens: number | undefined, budg
234
234
  maxTokens: number;
235
235
  budgetTokens: number;
236
236
  };
237
+ /**
238
+ * Whether the conversation has stopped being thinking-led, so routes with
239
+ * `compat.stripThinkingHistory` drop the budget `thinking` config and replay
240
+ * history without thinking blocks. Exported so the factory-droid provider can
241
+ * gate its header-level interleaved beta on the same decision.
242
+ */
243
+ export declare function shouldStripThinkingHistory(messages: readonly Message[]): boolean;
237
244
  /**
238
245
  * A single Anthropic conversation turn, including the mid-conversation
239
246
  * `system` role (Opus 4.8+ and Fable/Mythos 5).
@@ -1,10 +1,12 @@
1
- import type http2 from "node:http2";
2
1
  import { type InteractionQuery } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
3
2
  type ProtoUnknownField = {
4
3
  no: number;
5
4
  wireType: number;
6
5
  data: Uint8Array;
7
6
  };
7
+ interface CursorInteractionWriter {
8
+ write(frame: Uint8Array): void;
9
+ }
8
10
  /** Wrap one Connect-protocol message: 1 flag byte + 4-byte big-endian length + payload. */
9
11
  export declare function frameConnectMessage(data: Uint8Array, flags?: number): Buffer;
10
12
  /** Well-formed protobuf-es `$unknown` entries on `message`; anything else on the bag is ignored. */
@@ -21,5 +23,5 @@ export declare function protoUnknownFields(message: object): ProtoUnknownField[]
21
23
  * Unsupported interactive queries are rejected so the server is not stranded.
22
24
  * VM setup is left unanswered rather than reporting a fake success.
23
25
  */
24
- export declare function handleInteractionQuery(query: InteractionQuery, h2Request: http2.ClientHttp2Stream): void;
26
+ export declare function handleInteractionQuery(query: InteractionQuery, h2Request: CursorInteractionWriter): void;
25
27
  export {};
@@ -1,12 +1,12 @@
1
- import http2 from "node:http2";
2
1
  import type { CursorRule, McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
2
+ import { CURSOR_CLIENT_VERSION } from "@oh-my-pi/pi-catalog/wire/cursor";
3
3
  import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
4
4
  import { type JsonValue } from "@oh-my-pi/pi-catalog/discovery/protobuf";
5
5
  import type { AssistantMessage, Context, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
6
6
  import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, kStreamingEnvelopeId, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols.js";
7
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
8
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
9
- export declare const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
9
+ export { CURSOR_CLIENT_VERSION };
10
10
  export interface CursorOptions extends StreamOptions {
11
11
  customSystemPrompt?: string;
12
12
  conversationId?: string;
@@ -16,28 +16,31 @@ export interface CursorOptions extends StreamOptions {
16
16
  externalToolExecutor?: boolean;
17
17
  /** Wire model id selected after thinking-effort routing (`resolveWireModelId`). */
18
18
  wireModelId?: string;
19
+ /** Run transport. `auto` starts with HTTP/2 and falls back on failed ALPN negotiation. */
20
+ transport?: "auto" | "http2" | "http1";
19
21
  }
20
22
  interface CursorRequestState {
21
23
  conversationId: string;
22
24
  blobStore: Map<string, Uint8Array>;
23
25
  conversationState?: ConversationStateStructure;
24
- rotatedFresh?: boolean;
26
+ resume?: boolean;
25
27
  }
26
28
  interface CursorGrpcRequest {
27
29
  requestBytes: Uint8Array;
28
30
  blobStore: Map<string, Uint8Array>;
29
31
  conversationState: ConversationStateStructure;
30
32
  }
33
+ interface CursorMessageWriter {
34
+ write(frame: Uint8Array): void;
35
+ }
31
36
  /**
32
37
  * Maps an opaque HTTP/2 negotiation failure into an actionable error.
33
38
  *
34
- * bun only opens an HTTP/2 session when TLS-ALPN negotiates `h2`. Behind a
35
- * TLS-intercepting proxy that strips ALPN (e.g. Zscaler), the handshake yields
36
- * no `h2` protocol and bun throws `ERR_HTTP2_ERROR: h2 is not supported`. The
37
- * Cursor run RPC is HTTP/2-only (the ALB rejects HTTP/1.1 with 464), so there
38
- * is no h1 fallback the way model discovery has one — the run simply cannot
39
- * proceed. Replace the opaque message with one that names the cause and points
40
- * at the `providers.cursor.baseUrl` workaround.
39
+ * Bun only opens an HTTP/2 session when TLS-ALPN negotiates `h2`. Behind a
40
+ * TLS-intercepting proxy that strips ALPN, the handshake can fail with
41
+ * `ERR_HTTP2_ERROR: h2 is not supported`. Automatic transport retries the
42
+ * official RunSSE/BidiAppend HTTP/1 path; this mapped error remains observable
43
+ * when a caller explicitly forces HTTP/2.
41
44
  *
42
45
  * Non-ALPN errors pass through untouched.
43
46
  */
@@ -106,7 +109,7 @@ export interface UsageState {
106
109
  sawTokenDelta: boolean;
107
110
  }
108
111
  /** Exported for tests: drives one Cursor server message through the stream (exec waits mark the stream busy). */
109
- export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, h2Request: http2.ClientHttp2Stream, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], requestContextRules?: CursorRule[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void, externalToolExecutor?: boolean): Promise<void>;
112
+ export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, runTransport: CursorMessageWriter, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], requestContextRules?: CursorRule[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void, externalToolExecutor?: boolean): Promise<void>;
110
113
  /**
111
114
  * Exported for tests: verifies handler is invoked with correct `this` when passed as bound.
112
115
  *
@@ -259,4 +262,3 @@ export declare function buildCursorHistoryForTest(messages: Message[], activeUse
259
262
  };
260
263
  /** Builds the normalized Cursor Run request used by transport callers and request inspection hooks. */
261
264
  export declare function buildGrpcRequest(model: Model<"cursor-agent">, context: Context, options: CursorOptions | undefined, state: CursorRequestState): Promise<CursorGrpcRequest>;
262
- export {};
@@ -0,0 +1,15 @@
1
+ import type { RequestPolicy } from "@oh-my-pi/pi-catalog/compat/types";
2
+ import type { Effort } from "@oh-my-pi/pi-catalog/effort";
3
+ import type { Context, Model, StreamOptions } from "../../types.js";
4
+ import { AssistantMessageEventStream } from "../../utils/event-stream.js";
5
+ export interface FactoryDroidGeminiOptions extends StreamOptions {
6
+ /** Base URL including the `/api/llm/g/v1` namespace. */
7
+ baseUrl: string;
8
+ reasoning?: Effort;
9
+ disableReasoning?: boolean;
10
+ /** Additional droid identity headers (merged over the client's own). */
11
+ headers: Record<string, string>;
12
+ /** Thinking dialect selected by the route policy. */
13
+ thinkingDialect?: RequestPolicy["googleThinking"];
14
+ }
15
+ export declare function streamFactoryDroidGemini(model: Model<"factory-droid-agent">, context: Context, options: FactoryDroidGeminiOptions): AssistantMessageEventStream;
@@ -0,0 +1,13 @@
1
+ import type { Effort } from "@oh-my-pi/pi-catalog/effort";
2
+ import type { ServiceTier, StreamFunction, StreamOptions, ToolChoice } from "../types.js";
3
+ export interface FactoryDroidOptions extends StreamOptions {
4
+ reasoning?: Effort;
5
+ disableReasoning?: boolean;
6
+ toolChoice?: ToolChoice;
7
+ serviceTier?: ServiceTier;
8
+ /** OMP-native "omit thinking summaries" (anthropic adaptive display). */
9
+ hideThinkingSummary?: boolean;
10
+ /** OMP-native response verbosity (responses wire `text.verbosity`). */
11
+ textVerbosity?: "low" | "medium" | "high";
12
+ }
13
+ export declare const streamFactoryDroid: StreamFunction<"factory-droid-agent">;
@@ -75,6 +75,7 @@ export declare function isThinkingPart(part: Pick<Part, "thought" | "thoughtSign
75
75
  * a signature from being overwritten with `undefined` within the same streamed block.
76
76
  */
77
77
  export declare function retainThoughtSignature(existing: string | undefined, incoming: string | undefined): string | undefined;
78
+ export declare const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
78
79
  /**
79
80
  * Convert internal messages to Gemini Content[] format.
80
81
  */
@@ -212,3 +213,14 @@ export declare function streamGoogleGenAI<T extends "google-generative-ai" | "go
212
213
  retainTextSignature?: boolean;
213
214
  prepare: () => GoogleGenAIRequestPlan | Promise<GoogleGenAIRequestPlan>;
214
215
  }): AssistantMessageEventStream;
216
+ /**
217
+ * Human-readable message for a non-2xx Google response.
218
+ *
219
+ * On a usage-limit status the RPC `status`/`details` residue is kept after the
220
+ * message: `parseGoogleRpcRateLimitReason` reads `RESOURCE_EXHAUSTED` plus the
221
+ * `google.rpc.ErrorInfo` reason to tell an account billing cap (terminal) from
222
+ * a per-minute throttle (retryable), and reducing the body to `error.message`
223
+ * hid both, so every billing 429 replayed as a transient rate limit (#13090).
224
+ * The Cloud Code Assist path keeps the whole raw body for the same reason.
225
+ */
226
+ export declare function extractGoogleErrorMessage(errorText: string, status: number): string;
@@ -231,14 +231,19 @@ export interface ChatCompletionUserMessageParam {
231
231
  /** Optional participant name. */
232
232
  name?: string;
233
233
  }
234
+ /** Mistral Medium 3.5 reasoning content part on assistant history. */
235
+ export interface ChatCompletionMistralThinkingPart {
236
+ type: "thinking";
237
+ thinking: ChatCompletionContentPartText[];
238
+ }
234
239
  /** Assistant (model) message. */
235
240
  export interface ChatCompletionAssistantMessageParam {
236
241
  /** Always `assistant`. */
237
242
  role: "assistant";
238
243
  /** Data about a previous audio response from the model. */
239
244
  audio?: ChatCompletionAssistantMessageParamAudio | null;
240
- /** Message contents. Required unless `tool_calls` or `function_call` is specified. */
241
- content?: string | Array<ChatCompletionContentPartText | ChatCompletionContentPartRefusal> | null;
245
+ /** Message contents. Mistral reasoning uses typed thinking parts in this array. */
246
+ content?: string | Array<ChatCompletionContentPartText | ChatCompletionContentPartRefusal | ChatCompletionMistralThinkingPart> | null;
242
247
  /** Deprecated by OpenAI; replaced by `tool_calls`. */
243
248
  function_call?: ChatCompletionAssistantMessageParamFunctionCall | null;
244
249
  /** Optional participant name. */
@@ -42,5 +42,5 @@ export interface OpenAICompletionsOptions extends StreamOptions {
42
42
  * assistant output commits the attempt.
43
43
  */
44
44
  export declare const streamOpenAICompletions: StreamFunction<"openai-completions">;
45
- export declare function parseChunkUsage(rawUsage: object, model: Model<"openai-completions">, premiumRequests: number | undefined, timestamp?: number): AssistantMessage["usage"];
45
+ export declare function parseChunkUsage(rawUsage: object, model: Model<"openai-completions">, premiumRequests: number | undefined, timestamp?: number, cachedTokensHeader?: number): AssistantMessage["usage"];
46
46
  export declare function convertMessages(model: Model<"openai-completions">, context: Context, compat: ResolvedOpenAICompat): ChatCompletionMessageParam[];
@@ -12,8 +12,8 @@ export type OAuthDeviceCodePollResult<T> = {
12
12
  };
13
13
  /** Options for polling an RFC 8628-style OAuth device-code flow. */
14
14
  export interface OAuthDeviceCodeFlowOptions<T> {
15
- /** Poll the provider once and classify the response. */
16
- poll(): OAuthDeviceCodePollResult<T> | Promise<OAuthDeviceCodePollResult<T>>;
15
+ /** Poll the provider once and classify the response. Signal includes caller cancellation and device-code expiry. */
16
+ poll(signal?: AbortSignal): OAuthDeviceCodePollResult<T> | Promise<OAuthDeviceCodePollResult<T>>;
17
17
  /** Provider-requested polling cadence; defaults to RFC 8628's five seconds. */
18
18
  intervalSeconds?: number;
19
19
  /** Provider-issued expiry window for the device code. */
@@ -0,0 +1,3 @@
1
+ import type { AfterExchangeHook } from "../hooks/types.js";
2
+ /** Resolve canonical Factory identity; residency chooses the host, inference scope chooses eligible routes. */
3
+ export declare const attachFactoryDroidRegion: AfterExchangeHook;
@@ -11,13 +11,23 @@ export type OAuthCredentials = {
11
11
  apiEndpoint?: string;
12
12
  /**
13
13
  * Organization/workspace the token is scoped to (e.g. an Anthropic org
14
- * UUID or a ChatGPT workspace id). Captured once at login; token refreshes
15
- * never rewrite it. Lets one account email hold credentials for multiple
16
- * subscriptions.
14
+ * UUID or canonical Factory org ID). Lets one account email hold
15
+ * credentials for multiple subscriptions.
17
16
  */
18
17
  orgId?: string;
19
18
  /** Human-readable organization name for display (may embed the email). */
20
19
  orgName?: string;
20
+ /**
21
+ * Account residency region (e.g. `"eu"`), when the provider is
22
+ * region-partitioned. Captured at login; refreshed identity may update it
23
+ * when the account migrates, while failed lookups preserve the stored value.
24
+ * Residency selects the API host, not inference eligibility.
25
+ */
26
+ region?: string;
27
+ /** Factory organization inference scope, independent of account residency. */
28
+ inferenceRegion?: "global" | "eu" | "us";
29
+ /** WorkOS selected organization; never used as a Factory API organization header. */
30
+ activeOrganizationId?: string;
21
31
  /**
22
32
  * Epoch ms of the interactive login that minted this grant. Set by
23
33
  * `AuthStorage.oauth.login`; token refreshes preserve it. Providers with an
@@ -6,6 +6,7 @@ import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, Grep
6
6
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
7
7
  import type { Api, FetchImpl, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
8
8
  import type { ApiKey } from "./auth-retry.js";
9
+ import type { OAuthRequestIdentity } from "./auth/types.js";
9
10
  import type { BedrockOptions } from "./providers/amazon-bedrock.js";
10
11
  import type { AnthropicOptions } from "./providers/anthropic.js";
11
12
  import type { FallbackParam, StopDetails } from "./providers/anthropic-wire.js";
@@ -13,6 +14,7 @@ import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-respo
13
14
  import type { CursorOptions } from "./providers/cursor.js";
14
15
  import type { AppleFoundationModelsOptions } from "./providers/apple-foundation-models.js";
15
16
  import type { DevinOptions } from "./providers/devin.js";
17
+ import type { FactoryDroidOptions } from "./providers/factory-droid.js";
16
18
  import type { GitLabDuoWorkflowOptions } from "./providers/gitlab-duo-workflow.js";
17
19
  import type { GoogleOptions } from "./providers/google.js";
18
20
  import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli.js";
@@ -49,6 +51,7 @@ export interface ApiOptionsMap {
49
51
  "google-vertex": GoogleVertexOptions;
50
52
  "ollama-chat": OllamaChatOptions;
51
53
  "cursor-agent": CursorOptions;
54
+ "factory-droid-agent": FactoryDroidOptions;
52
55
  "gitlab-duo-agent": GitLabDuoWorkflowOptions;
53
56
  "devin-agent": DevinOptions;
54
57
  "apple-foundation-models": AppleFoundationModelsOptions;
@@ -247,6 +250,8 @@ export interface StreamOptions {
247
250
  apiKey?: string;
248
251
  /** @internal Stored credential row serving this request, when known. */
249
252
  credentialId?: number;
253
+ /** @internal Non-secret identity of the bearer serving this attempt; never persisted in history. */
254
+ oauthIdentity?: OAuthRequestIdentity;
250
255
  cacheRetention?: CacheRetention;
251
256
  /**
252
257
  * Anthropic preserved-thinking behavior when a signed block no longer matches
@@ -0,0 +1,11 @@
1
+ import type { CredentialRankingStrategy, UsageProvider, UsageReport } from "../usage.js";
2
+ /**
3
+ * Parses `GET /api/billing/limits` into a usage report: per-pool
4
+ * (Standard credits / Droid Core)
5
+ * × per-window (5h / weekly / monthly) percent-used limits, plus the extra
6
+ * usage balance when present.
7
+ */
8
+ export declare function parseFactoryDroidUsage(payload: unknown, fetchedAt?: number): UsageReport | null;
9
+ export declare const factoryDroidUsageProvider: UsageProvider;
10
+ /** Factory's Core and Standard credits have independent subscription windows. */
11
+ export declare const factoryDroidRankingStrategy: CredentialRankingStrategy;
@@ -563,6 +563,10 @@ export interface UsageCredential {
563
563
  /** Human-readable organization name for display. */
564
564
  orgName?: string;
565
565
  enterpriseUrl?: string;
566
+ /** Account residency used for region-aware provider routing. */
567
+ region?: string;
568
+ inferenceRegion?: "global" | "eu" | "us";
569
+ activeOrganizationId?: string;
566
570
  metadata?: Record<string, unknown>;
567
571
  apiEndpoint?: string;
568
572
  }
@@ -0,0 +1,2 @@
1
+ /** Project a dereferenced JSON Schema onto Factory Gemini's allowed fields. */
2
+ export declare function normalizeSchemaForFactoryDroid(value: unknown): unknown;
@@ -1,4 +1,5 @@
1
1
  export * from "./adapt.js";
2
+ export * from "./allowlist.js";
2
3
  export * from "./compatibility.js";
3
4
  export * from "./dereference.js";
4
5
  export * from "./draft.js";