@oh-my-pi/pi-ai 17.2.7 → 17.2.9

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 (39) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
  3. package/dist/types/auth-storage.d.ts +1 -108
  4. package/dist/types/error/flags.d.ts +4 -2
  5. package/dist/types/error/index.d.ts +1 -0
  6. package/dist/types/error/rate-limit.d.ts +21 -5
  7. package/dist/types/error/retryable.d.ts +1 -1
  8. package/dist/types/providers/anthropic.d.ts +2 -2
  9. package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
  10. package/dist/types/providers/cursor-pi-args.d.ts +6 -0
  11. package/dist/types/providers/mock.d.ts +1 -0
  12. package/dist/types/providers/openai-codex/request-transformer.d.ts +1 -1
  13. package/dist/types/providers/openai-codex-responses.d.ts +1 -1
  14. package/dist/types/providers/pi-native-server.d.ts +1 -1
  15. package/dist/types/utils/http-inspector.d.ts +5 -4
  16. package/dist/types/utils/retry.d.ts +3 -2
  17. package/package.json +5 -5
  18. package/src/auth/sqlite-credential-store.ts +2049 -0
  19. package/src/auth-retry.ts +8 -5
  20. package/src/auth-storage.ts +30 -2023
  21. package/src/error/auth-classify.ts +5 -4
  22. package/src/error/flags.ts +74 -18
  23. package/src/error/index.ts +1 -0
  24. package/src/error/rate-limit.ts +64 -5
  25. package/src/error/retryable.ts +1 -1
  26. package/src/providers/anthropic.ts +15 -3
  27. package/src/providers/cursor/exec-modern.ts +1 -0
  28. package/src/providers/cursor-pi-args.ts +30 -0
  29. package/src/providers/cursor.ts +16 -9
  30. package/src/providers/mock.ts +1 -1
  31. package/src/providers/openai-codex/request-transformer.ts +21 -27
  32. package/src/providers/openai-codex-responses.ts +2 -2
  33. package/src/providers/pi-native-client.ts +1 -1
  34. package/src/providers/pi-native-server.ts +1 -1
  35. package/src/registry/oauth/devin.ts +1 -1
  36. package/src/stream.ts +3 -3
  37. package/src/usage/openai-codex.ts +30 -19
  38. package/src/utils/http-inspector.ts +6 -5
  39. package/src/utils/retry.ts +24 -5
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.2.9] - 2026-08-05
6
+
7
+ ### Fixed
8
+
9
+ - Fixed GitHub Copilot requests failing with a raw `HTTP 400 model_not_available_for_integrator` on roughly half of all turns for recently rolled-out models. Copilot's fleet is not uniform — part of it rejects models that `/models` advertises on the same host — and the transient classifier matched only the older `model_not_supported` code at a fixed envelope depth, so these rejections surfaced as terminal errors instead of entering the existing retry path. Model-availability 400s are now recognized at any envelope depth and rerolled on a flat delay with a dedicated 8-attempt budget on the OpenAI transports; every other retryable failure keeps its previous backoff and attempt count.
10
+ - Fixed Cursor reads with inline OMP range selectors reporting the returned slice length as the source file's `totalLines`, which made sequential reads of an unchanged file appear inconsistent ([#7590](https://github.com/can1357/oh-my-pi/issues/7590)).
11
+ - Made model-scoped usage health ignore Codex accounts that cannot use the requested plan-gated model while retaining conservative unknown-state handling and independent usage-window resets.
12
+ - Fixed OpenAI Codex usage telemetry blocking explicitly allowed ChatGPT Team credentials when a weekly `used_percent` rounded to 100, which could route multi-account sessions to an actually exhausted sibling instead ([#7617](https://github.com/can1357/oh-my-pi/issues/7617)).
13
+ - Fixed OpenAI Codex GPT-5.x requests sending optional `reasoning.summary`, `reasoning.context`, and `text.verbosity` controls by default, reducing Codex `server_error` disconnects from unsupported request shapes. ([#4949](https://github.com/can1357/oh-my-pi/issues/4949))
14
+ - Classified concurrent-request caps separately from quota exhaustion so they use a short retry backoff without burning a credential, and rotate credentials for account-scoped 403 caps such as Devin's overall message limit.
15
+
5
16
  ## [17.2.7] - 2026-08-03
6
17
 
7
18
  ### Changed
@@ -0,0 +1,117 @@
1
+ /**
2
+ * SQLite-backed credential persistence for AuthStorage.
3
+ *
4
+ * The public AuthCredentialStore interface remains in ../auth-storage so local
5
+ * and remote stores share the same contract.
6
+ */
7
+ import { Database } from "bun:sqlite";
8
+ import type { AuthCredential, AuthCredentialStore, CredentialRefreshLeaseFence, DisabledCredentialSummary, StoredAuthCredential, StoredCredentialBlock } from "../auth-storage.js";
9
+ import type { OAuthCredentials } from "../registry/oauth/types.js";
10
+ import type { ClientUsageReport, ClientUsageSummary, UsageCostHistoryEntry, UsageCostHistoryQuery, UsageHistoryEntry, UsageHistoryQuery } from "../usage.js";
11
+ export declare const USAGE_REPORT_TTL_MS: number;
12
+ type SerializedCredentialRecord = {
13
+ credentialType: AuthCredential["type"];
14
+ data: string;
15
+ identityKey: string | null;
16
+ };
17
+ /**
18
+ * SQLite's busy result code family — base `SQLITE_BUSY` plus the extended
19
+ * variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
20
+ * and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
21
+ */
22
+ export declare function isSqliteBusyError(err: unknown): boolean;
23
+ /**
24
+ * SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
25
+ * (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
26
+ * and `SQLITE_NOTADB` (the file header is not a database). Unlike
27
+ * {@link isSqliteBusyError}, these never clear by retrying: the store must be
28
+ * repaired or replaced, so callers latch and stop touching it.
29
+ */
30
+ export declare function isSqliteCorruptionError(err: unknown): boolean;
31
+ export declare function serializeCredential(provider: string, credential: AuthCredential): SerializedCredentialRecord | null;
32
+ export declare function resolveCredentialIdentityKey(provider: string, credential: AuthCredential): string | null;
33
+ /**
34
+ * Default SQLite-backed implementation of {@link AuthCredentialStore}.
35
+ *
36
+ * Used by the pi-ai CLI and as the default store for `AuthStorage.create()`.
37
+ * Also exposes convenience methods (`saveOAuth`, `getOAuth`, `saveApiKey`,
38
+ * `getApiKey`, `listProviders`, `deleteProvider`) that callers can use directly
39
+ * without going through `AuthStorage`.
40
+ */
41
+ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
42
+ #private;
43
+ constructor(db: Database);
44
+ static open(dbPath?: string): Promise<SqliteAuthCredentialStore>;
45
+ listAuthCredentials(provider?: string): StoredAuthCredential[];
46
+ listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]>;
47
+ replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
48
+ upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
49
+ updateAuthCredential(id: number, credential: AuthCredential): void;
50
+ tryUpdateAuthCredentialIfMatches(id: number, expectedData: string, credential: AuthCredential, lease?: CredentialRefreshLeaseFence): boolean;
51
+ deleteAuthCredential(id: number, disabledCause: string): void;
52
+ /**
53
+ * CAS-style disable: only soft-deletes the row when its `data` column still
54
+ * matches `expectedData` and the row has not already been disabled. Used by
55
+ * the OAuth refresh-failure path to avoid clobbering a peer that rotated the
56
+ * row between our pre-check and the disable.
57
+ */
58
+ tryDisableAuthCredentialIfMatches(id: number, expectedData: string, disabledCause: string, lease?: CredentialRefreshLeaseFence): boolean;
59
+ deleteAuthCredentialsForProvider(provider: string, disabledCause: string): void;
60
+ getCache(key: string, options?: {
61
+ includeExpired?: boolean;
62
+ }): string | null;
63
+ setCache(key: string, value: string, expiresAtSec: number): void;
64
+ /** Drop all cache rows whose keys start with the supplied prefix. */
65
+ deleteCachePrefix(prefix: string): void;
66
+ cleanExpiredCache(): void;
67
+ getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined;
68
+ getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined;
69
+ upsertCredentialBlock(block: StoredCredentialBlock): void;
70
+ deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void;
71
+ deleteCredentialBlocks(credentialId: number): void;
72
+ cleanExpiredCredentialBlocks(nowMs: number): void;
73
+ listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[];
74
+ tryAcquireCredentialRefreshLease(credentialId: number, owner: string, expiresAtMs: number): boolean;
75
+ getCredentialRefreshLeaseExpiresAt(credentialId: number): number | undefined;
76
+ renewCredentialRefreshLease(credentialId: number, owner: string, expiresAtMs: number): boolean;
77
+ releaseCredentialRefreshLease(credentialId: number, owner: string): void;
78
+ recordUsageSnapshots(entries: UsageHistoryEntry[]): void;
79
+ listUsageHistory(query?: UsageHistoryQuery): UsageHistoryEntry[];
80
+ recordUsageCosts(entries: UsageCostHistoryEntry[]): void;
81
+ listUsageCosts(query?: UsageCostHistoryQuery): UsageCostHistoryEntry[];
82
+ recordClientUsage(report: ClientUsageReport): void;
83
+ getClientUsageSummary(sinceMs: number): ClientUsageSummary;
84
+ /**
85
+ * Save OAuth credentials for a provider.
86
+ * Preserves unrelated identities and replaces only the matching credential.
87
+ */
88
+ saveOAuth(provider: string, credentials: OAuthCredentials): void;
89
+ /**
90
+ * Get OAuth credentials for a provider.
91
+ */
92
+ getOAuth(provider: string): OAuthCredentials | null;
93
+ /**
94
+ * Save API key for a provider (replaces existing).
95
+ */
96
+ saveApiKey(provider: string, apiKey: string): void;
97
+ /**
98
+ * Get API key for a provider.
99
+ */
100
+ getApiKey(provider: string): string | null;
101
+ /**
102
+ * List all providers with credentials.
103
+ */
104
+ listProviders(): string[];
105
+ /**
106
+ * Delete all credentials for a provider.
107
+ */
108
+ deleteProvider(provider: string): void;
109
+ /**
110
+ * SQLite increments `data_version` when another connection commits. Own
111
+ * writes leave it unchanged and already notify AuthStorage directly.
112
+ */
113
+ pollExternalChanges(): boolean;
114
+ acknowledgeLocalChanges(): void;
115
+ close(): void;
116
+ }
117
+ export {};
@@ -1,18 +1,9 @@
1
- /**
2
- * Credential storage for API keys and OAuth tokens.
3
- * Handles loading, saving, refreshing credentials, and usage tracking.
4
- *
5
- * This module defines:
6
- * - `AuthCredentialStore` interface: persistence abstraction (SQLite, remote vault, …)
7
- * - `AuthStorage` class: credential management with round-robin, usage limits, OAuth refresh
8
- * - `SqliteAuthCredentialStore`: concrete SQLite-backed implementation
9
- */
10
- import { Database } from "bun:sqlite";
11
1
  import type { ApiKeyResolver } from "./auth-retry.js";
12
2
  import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthProviderId } from "./registry/oauth/types.js";
13
3
  import type { Provider } from "./types.js";
14
4
  import type { ClientUsageReport, ClientUsageSummary, CredentialRankingStrategy, ObservedUsageEntry, UsageCostHistoryEntry, UsageCostHistoryQuery, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage.js";
15
5
  import { type CodexResetConsumeCode, type CodexResetCredit } from "./usage/openai-codex-reset.js";
6
+ export { isSqliteBusyError, isSqliteCorruptionError, SqliteAuthCredentialStore, } from "./auth/sqlite-credential-store.js";
16
7
  export type ApiKeyCredential = {
17
8
  type: "api_key";
18
9
  key: string;
@@ -1265,101 +1256,3 @@ export declare class AuthStorage {
1265
1256
  */
1266
1257
  describeCredentialSource(provider: string, sessionId?: string): string | undefined;
1267
1258
  }
1268
- /**
1269
- * SQLite's busy result code family — base `SQLITE_BUSY` plus the extended
1270
- * variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
1271
- * and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
1272
- */
1273
- export declare function isSqliteBusyError(err: unknown): boolean;
1274
- /**
1275
- * SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
1276
- * (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
1277
- * and `SQLITE_NOTADB` (the file header is not a database). Unlike
1278
- * {@link isSqliteBusyError}, these never clear by retrying: the store must be
1279
- * repaired or replaced, so callers latch and stop touching it.
1280
- */
1281
- export declare function isSqliteCorruptionError(err: unknown): boolean;
1282
- /**
1283
- * Default SQLite-backed implementation of {@link AuthCredentialStore}.
1284
- *
1285
- * Used by the pi-ai CLI and as the default store for `AuthStorage.create()`.
1286
- * Also exposes convenience methods (`saveOAuth`, `getOAuth`, `saveApiKey`,
1287
- * `getApiKey`, `listProviders`, `deleteProvider`) that callers can use directly
1288
- * without going through `AuthStorage`.
1289
- */
1290
- export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
1291
- #private;
1292
- constructor(db: Database);
1293
- static open(dbPath?: string): Promise<SqliteAuthCredentialStore>;
1294
- listAuthCredentials(provider?: string): StoredAuthCredential[];
1295
- listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]>;
1296
- replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
1297
- upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
1298
- updateAuthCredential(id: number, credential: AuthCredential): void;
1299
- tryUpdateAuthCredentialIfMatches(id: number, expectedData: string, credential: AuthCredential, lease?: CredentialRefreshLeaseFence): boolean;
1300
- deleteAuthCredential(id: number, disabledCause: string): void;
1301
- /**
1302
- * CAS-style disable: only soft-deletes the row when its `data` column still
1303
- * matches `expectedData` and the row has not already been disabled. Used by
1304
- * the OAuth refresh-failure path to avoid clobbering a peer that rotated the
1305
- * row between our pre-check and the disable.
1306
- */
1307
- tryDisableAuthCredentialIfMatches(id: number, expectedData: string, disabledCause: string, lease?: CredentialRefreshLeaseFence): boolean;
1308
- deleteAuthCredentialsForProvider(provider: string, disabledCause: string): void;
1309
- getCache(key: string, options?: {
1310
- includeExpired?: boolean;
1311
- }): string | null;
1312
- setCache(key: string, value: string, expiresAtSec: number): void;
1313
- /** Drop all cache rows whose keys start with the supplied prefix. */
1314
- deleteCachePrefix(prefix: string): void;
1315
- cleanExpiredCache(): void;
1316
- getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined;
1317
- getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined;
1318
- upsertCredentialBlock(block: StoredCredentialBlock): void;
1319
- deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void;
1320
- deleteCredentialBlocks(credentialId: number): void;
1321
- cleanExpiredCredentialBlocks(nowMs: number): void;
1322
- listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[];
1323
- tryAcquireCredentialRefreshLease(credentialId: number, owner: string, expiresAtMs: number): boolean;
1324
- getCredentialRefreshLeaseExpiresAt(credentialId: number): number | undefined;
1325
- renewCredentialRefreshLease(credentialId: number, owner: string, expiresAtMs: number): boolean;
1326
- releaseCredentialRefreshLease(credentialId: number, owner: string): void;
1327
- recordUsageSnapshots(entries: UsageHistoryEntry[]): void;
1328
- listUsageHistory(query?: UsageHistoryQuery): UsageHistoryEntry[];
1329
- recordUsageCosts(entries: UsageCostHistoryEntry[]): void;
1330
- listUsageCosts(query?: UsageCostHistoryQuery): UsageCostHistoryEntry[];
1331
- recordClientUsage(report: ClientUsageReport): void;
1332
- getClientUsageSummary(sinceMs: number): ClientUsageSummary;
1333
- /**
1334
- * Save OAuth credentials for a provider.
1335
- * Preserves unrelated identities and replaces only the matching credential.
1336
- */
1337
- saveOAuth(provider: string, credentials: OAuthCredentials): void;
1338
- /**
1339
- * Get OAuth credentials for a provider.
1340
- */
1341
- getOAuth(provider: string): OAuthCredentials | null;
1342
- /**
1343
- * Save API key for a provider (replaces existing).
1344
- */
1345
- saveApiKey(provider: string, apiKey: string): void;
1346
- /**
1347
- * Get API key for a provider.
1348
- */
1349
- getApiKey(provider: string): string | null;
1350
- /**
1351
- * List all providers with credentials.
1352
- */
1353
- listProviders(): string[];
1354
- /**
1355
- * Delete all credentials for a provider.
1356
- */
1357
- deleteProvider(provider: string): void;
1358
- /**
1359
- * SQLite increments `data_version` when another connection commits. Own
1360
- * writes leave it unchanged and already notify AuthStorage directly.
1361
- */
1362
- pollExternalChanges(): boolean;
1363
- acknowledgeLocalChanges(): void;
1364
- close(): void;
1365
- }
@@ -65,8 +65,10 @@ export declare function isGrammarError(error: unknown): boolean;
65
65
  */
66
66
  export declare function isFastModeUnsupported(error: unknown): boolean;
67
67
  /**
68
- * GitHub Copilot 400 `model_not_supported` routing flap transient. Reads the
69
- * structural `code` (and falls back to {@link Flag.Transient} text classification).
68
+ * GitHub Copilot 400 rejecting a model its own `/models` catalog advertises —
69
+ * transient fleet skew, not a malformed request. Reads the structural `code`
70
+ * through the SDK/body envelopes, then falls back to the stringified body both
71
+ * SDK families put in `message` (shapes drift; the wire text does not).
70
72
  */
71
73
  export declare function isCopilotTransientModelError(error: unknown): boolean;
72
74
  export declare function classifyMessage(message: {
@@ -9,5 +9,6 @@ export * from "./format.js";
9
9
  export * from "./gateway.js";
10
10
  export * from "./oauth.js";
11
11
  export * from "./provider.js";
12
+ export * from "./rate-limit.js";
12
13
  export * from "./retryable.js";
13
14
  export * from "./validation.js";
@@ -2,12 +2,12 @@
2
2
  * Rate limit reason classification and backoff calculation utilities.
3
3
  * Ported from opencode-antigravity-auth plugin for consistency.
4
4
  */
5
- export type RateLimitReason = "QUOTA_EXHAUSTED" | "RATE_LIMIT_EXCEEDED" | "MODEL_CAPACITY_EXHAUSTED" | "SERVER_ERROR" | "UNKNOWN";
5
+ export type RateLimitReason = "QUOTA_EXHAUSTED" | "RATE_LIMIT_EXCEEDED" | "CONCURRENT_LIMIT" | "MODEL_CAPACITY_EXHAUSTED" | "SERVER_ERROR" | "UNKNOWN";
6
6
  /**
7
7
  * Classify a rate-limit error message into a reason category.
8
8
  * Priority order: explicit details in a resource-exhausted error > QUOTA
9
- * (Antigravity "quota will reset") > MODEL_CAPACITY > QUOTA (account) >
10
- * RATE_LIMIT > QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN.
9
+ * (Antigravity "quota will reset") > CONCURRENT_LIMIT > MODEL_CAPACITY >
10
+ * QUOTA (account) > RATE_LIMIT > QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN.
11
11
  *
12
12
  * Bare "resource exhausted" / "resource_exhausted" maps to MODEL_CAPACITY (transient, short wait).
13
13
  * Explicit details such as "quota exceeded" retain their normal classification.
@@ -39,8 +39,10 @@ export declare function isUsageLimitStatus(status: number | undefined): boolean;
39
39
  * 3. Body is absent or {@link isOpaqueStatusBody opaque} (just the status,
40
40
  * empty JSON, HTTP framing only) → rotate conservatively: the server
41
41
  * gave us nothing else to go on.
42
- * 4. Body has content → defer to {@link parseRateLimitReason}. Only
43
- * `QUOTA_EXHAUSTED` rotates; `RATE_LIMIT_EXCEEDED` (`Too many requests`,
42
+ * 4. Body has content → defer to {@link parseRateLimitReason}. `QUOTA_EXHAUSTED`
43
+ * rotates; for the categorical 402 billing cap a `CONCURRENT_LIMIT` body
44
+ * also rotates (the cap is concurrent-worded but the status is still an
45
+ * exhausted billing cap). `RATE_LIMIT_EXCEEDED` (`Too many requests`,
44
46
  * per-minute caps), `MODEL_CAPACITY_EXHAUSTED` (`Service overloaded`),
45
47
  * `SERVER_ERROR`, and `UNKNOWN` (`Please retry in 5s`) stay in the
46
48
  * provider's own backoff layer so transient 429s don't burn sibling
@@ -62,3 +64,17 @@ export declare function isOpaqueStatusBody(message: string): boolean;
62
64
  * {@link isUsageLimitOutcome} uses it for the account-rotation decision.
63
65
  */
64
66
  export declare function matchesUsageLimitText(errorMessage: string): boolean;
67
+ /**
68
+ * Account-scoped cap phrasing delivered on a 403 (or a statusless Connect
69
+ * trailer): "Reached overall message rate limit", "Your limit will reset in …".
70
+ * Kept separate from {@link matchesUsageLimitText} because the bare wording is
71
+ * ambiguous without the 403 / statusless-account context; consumed by both
72
+ * {@link isUsageLimitOutcome} (rotation decision) and `flags.ts` (Flag.UsageLimit).
73
+ */
74
+ export declare function isAccountScopedCapText(message: string): boolean;
75
+ /**
76
+ * A concurrency cap on a non-billing status is shed-and-backoff, not
77
+ * credential-rotatable. This mirrors the exclusion in {@link isUsageLimitOutcome}
78
+ * for the 403 auth-retry entry points. A 402 remains a categorical billing cap.
79
+ */
80
+ export declare function isConcurrencyCapExclusion(status: number | undefined, message: string | undefined): boolean;
@@ -11,7 +11,7 @@ export declare function isTransientStatus(status: number | undefined): boolean;
11
11
  export interface ProviderRetryableHooks {
12
12
  /** Provider id of the failing request, used to gate provider-specific checks. */
13
13
  provider?: string;
14
- /** Provider-specific transient predicate (e.g. Copilot `model_not_supported`). */
14
+ /** Provider-specific transient predicate (e.g. Copilot model-availability 400s). */
15
15
  isProviderTransient?: (error: Error) => boolean;
16
16
  }
17
17
  /**
@@ -188,8 +188,8 @@ export declare function resolveAnthropicCustomHeadersForBaseUrl(baseUrl: string
188
188
  /**
189
189
  * Whether an Anthropic (or Copilot-over-Anthropic) stream error should be
190
190
  * retried. The classification lives in {@link AIError.isProviderRetryableError};
191
- * this wrapper injects the Copilot-specific `model_not_supported` transient
192
- * check, which the error module must not import directly.
191
+ * this wrapper injects the Copilot-specific model-availability transient check,
192
+ * which the error module must not import directly.
193
193
  */
194
194
  export declare function isProviderRetryableError(error: unknown, provider?: string): boolean;
195
195
  export type AnthropicUsageLike = {
@@ -17,7 +17,7 @@ import type { ToolResultMessage } from "../../types.js";
17
17
  * virtual registry. Re-exported here because this is where the frame builders
18
18
  * and their translation are consumed together.
19
19
  */
20
- export { piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piTimeout, } from "../cursor-pi-args.js";
20
+ export { piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, } from "../cursor-pi-args.js";
21
21
  /** Flatten a tool result's content into the single `output` string the Pi frames carry. */
22
22
  export declare function piOutputText(toolResult: ToolResultMessage): string;
23
23
  /**
@@ -36,6 +36,12 @@
36
36
  * A range-free read keeps the ordinary form — whole-file reads want them.
37
37
  */
38
38
  export declare function piReadPath(readPath: string, offset?: number, limit?: number): string | null;
39
+ /**
40
+ * Whether a read path ends in an OMP line selector, including compound `raw`
41
+ * forms. Cursor uses this only to describe the operation already executed by
42
+ * the coding-agent read tool; the selector remains embedded in the path.
43
+ */
44
+ export declare function piReadPathHasRange(readPath: string): boolean;
39
45
  /**
40
46
  * The same range as {@link piReadPath}, rendered for a transcript block rather
41
47
  * than for execution.
@@ -53,6 +53,7 @@ export type MockContent = string | {
53
53
  } | {
54
54
  type: "thinking";
55
55
  thinking: string;
56
+ thinkingSignature?: string;
56
57
  } | {
57
58
  type: "toolCall";
58
59
  /** Optional explicit id; auto-generated when omitted. */
@@ -14,7 +14,7 @@ export interface CodexRequestOptions {
14
14
  /** User-facing effort; maps 1:1 onto the wire tier of the same name. */
15
15
  reasoningEffort?: CodexCallerEffort | "none";
16
16
  reasoningSummary?: ReasoningConfig["summary"] | null;
17
- /** Explicit `reasoning.context` override; defaults to `all_turns` when unset. Gated to gpt-5.4+ Codex models (older ids reject it, so it is suppressed and `context` omitted). Note that under Responses Lite (`responsesLite`), the server strictly requires `reasoning.context` to be `all_turns`, which overrides this option and forces `all_turns`. */
17
+ /** Explicit `reasoning.context` override. Omitted by default; Responses Lite forces `all_turns` as required by that transport. */
18
18
  reasoningContext?: CodexReasoningContext;
19
19
  textVerbosity?: "low" | "medium" | "high";
20
20
  include?: string[];
@@ -4,7 +4,7 @@ import type { ResponseInput } from "./openai-responses-wire.js";
4
4
  export interface OpenAICodexResponsesOptions extends StreamOptions {
5
5
  reasoning?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
6
6
  reasoningSummary?: "auto" | "concise" | "detailed" | null;
7
- /** `reasoning.context` replay scope; defaults to `all_turns` when unset. The `all_turns` value is gated to gpt-5.4+ Codex models older ids reject it, so it is suppressed and `context` omitted. */
7
+ /** Explicit `reasoning.context` replay scope. Omitted by default so Codex applies its native request policy. */
8
8
  reasoningContext?: CodexReasoningContext;
9
9
  textVerbosity?: "low" | "medium" | "high";
10
10
  codexMode?: boolean;
@@ -4,7 +4,7 @@
4
4
  * Where the OpenAI / Anthropic / Responses route modules translate foreign
5
5
  * wire shapes through pi-ai's canonical {@link Context}, this module accepts
6
6
  * the canonical shape *directly* — for clients that already speak pi-ai
7
- * (containerized omp, the swarm extension, robomp's sidecar auth-gateway).
7
+ * (containerized omp, robomp's sidecar auth-gateway).
8
8
  * Skipping the wire-format → Context → wire-format round-trip cuts
9
9
  * per-request CPU but, more importantly, avoids the quantization that those
10
10
  * translations impose on first-class pi-ai fields (service tier, cache
@@ -38,10 +38,11 @@ export declare function finalizeErrorMessage(error: unknown, rawRequestDump: Raw
38
38
  * Rewrite error message for GitHub Copilot request failures.
39
39
  * Must run AFTER finalizeErrorMessage since it replaces the message entirely.
40
40
  *
41
- * 400 `model_not_supported` = Copilot routing rollout gap for our OAuth client.
42
- * A preview model (gpt-5.3-codex, gpt-5.4*, ...) flaps between 200 and
43
- * 400 because only some of Copilot's backends have the model. After the
44
- * in-request retry exhausts, surface guidance rather than the raw error.
41
+ * 400 model-unavailable = Copilot fleet skew. A model that `/models` advertises
42
+ * (claude-sonnet-4.6, claude-opus-4.6, gpt-5.4, gpt-5.3-codex, ...)
43
+ * flaps between 200 and 400 because only part of Copilot's fleet has it
44
+ * in the integrator allowlist. After the in-request retry exhausts,
45
+ * surface guidance rather than the raw error.
45
46
  * 401 = token invalid/expired → credential removal is safe, prompt re-login.
46
47
  * 403 = token valid but access denied (plan, model policy, org restriction) →
47
48
  * do NOT reuse the auth-failed string (which triggers credential removal).
@@ -1,8 +1,9 @@
1
1
  import { isCopilotTransientModelError } from "../error/flags.js";
2
2
  export { isCopilotTransientModelError };
3
3
  /**
4
- * Wrap an initial Copilot request so transient `model_not_supported` 400s are
5
- * retried a small number of times. No-op for non-Copilot providers.
4
+ * Wrap an initial Copilot request so transient model-availability 400s
5
+ * (`model_not_supported`, `model_not_available_for_integrator`) are retried a
6
+ * small number of times. No-op for non-Copilot providers.
6
7
  *
7
8
  * The callback **MUST** create a fresh in-flight request each invocation — a
8
9
  * once-consumed AsyncIterable cannot be re-iterated.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "17.2.7",
4
+ "version": "17.2.9",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -38,10 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/omptype": "17.2.7",
42
- "@oh-my-pi/pi-catalog": "17.2.7",
43
- "@oh-my-pi/pi-utils": "17.2.7",
44
- "@oh-my-pi/pi-wire": "17.2.7",
41
+ "@oh-my-pi/omptype": "17.2.9",
42
+ "@oh-my-pi/pi-catalog": "17.2.9",
43
+ "@oh-my-pi/pi-utils": "17.2.9",
44
+ "@oh-my-pi/pi-wire": "17.2.9",
45
45
  "zod": "^4"
46
46
  },
47
47
  "devDependencies": {