@oh-my-pi/pi-ai 15.11.4 → 15.11.7

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 CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.11.7] - 2026-06-12
6
+
7
+ ### Added
8
+
9
+ - Added `requestModelId` and `thinking.suppress` options to `google-gemini-cli` so collapsed effort-tier variants serialize their per-effort upstream wire id, and thinking-off requests on models with `thinking.suppressWhenOff` send an explicit `thinkingConfig` (`includeThoughts: false` with `thinkingLevel: "MINIMAL"` or `thinkingBudget: 0`) — Cloud Code Assist re-applies the per-id baked server default when the config is omitted, silently thinking and billing the tokens
10
+ - Added mandatory-reasoning clamping: models baked with `thinking.requiresEffort` floor omitted or disabled reasoning to the lowest supported effort in every api mapping, and `disableReasoning` no longer emits OpenRouter `reasoning: { enabled: false }` for them — fixes `omp bench` and utility requests 400ing with "Reasoning is mandatory for this endpoint and cannot be disabled" on OpenRouter Gemini 3.x
11
+
12
+ ### Changed
13
+
14
+ - Changed `google-gemini-cli` request mapping to route per-request wire ids via `resolveWireModelId`: the session effort picks the backing variant id (collapsed `gemini-3.5-flash` at high → `gemini-3.5-flash-low`; claude pairs route off → bare id, efforts → `-thinking`) while `AssistantMessage.model` and usage attribution stay on the logical id. A thinking budget clamped to zero now falls through to the thinking-off path (off routing plus suppression) instead of only disabling thinking
15
+ - Changed `openai-completions` and `anthropic-messages` to serialize per-request wire ids via `resolveWireModelId`, so collapsed `X`/`X-thinking` pairs on aggregators and custom providers switch to the thinking SKU when reasoning is enabled (previously only `google-gemini-cli` routed effort-tier variants)
16
+
17
+ ### Fixed
18
+
19
+ - Fixed `google-gemini-cli` ignoring `Model.requestModelId` when serializing the request model id
20
+
21
+ ## [15.11.5] - 2026-06-12
22
+
23
+ ### Added
24
+
25
+ - Added `AuthStorage.listUsageHistory` to retrieve historical usage snapshots with optional `provider` and `sinceMs` filtering
26
+ - Added durable usage-history persistence in the sqlite auth store so successful usage reports are recorded as time-series snapshots of limit utilization for later trend inspection
27
+ - Added `AuthStorage.redeemResetCredit` to redeem stored OpenAI Codex saved rate-limit reset credits for a target account by `credentialId`, `accountId`, or `email`
28
+ - Added `listCodexResetCredits` and `consumeCodexResetCredit` exports for OpenAI Codex saved reset-credit listing and redemption
29
+ - Added `resetCredits` with `availableCount` to `UsageReport` so OpenAI Codex usage data now exposes redeemable rate-limit resets
30
+ - Added `openai-codex-reset` exports via package barrel for out-of-band tooling usage
31
+ - Added a one-shot request-debug target that writes the next provider HTTP request JSON to an explicit path.
32
+
33
+ ### Changed
34
+
35
+ - Changed `AuthStorage.redeemResetCredit` to invalidate cached usage data after a successful redemption so the next usage report reflects the reset immediately
36
+
37
+ ### Fixed
38
+
39
+ - Fixed temporary credential block state so redeemed reset credits immediately make the affected account selectable again after `redeemResetCredit` succeeds
40
+ - Fixed one-shot request-debug path handling so an explicit request log target is consumed after the next request and no longer affects subsequent calls
41
+ - Fixed explicit request-debug path mode to create missing parent directories before writing request logs
42
+ - Fixed explicit request-debug mode to overwrite existing `.res.log` files for the requested path instead of failing when they already exist
43
+ - Fixed OpenAI Responses `previous_response_id` chaining on Zero Data Retention orgs: the in-provider retry classifier missed the ZDR-specific 400 ("Previous response cannot be used for this organization due to Zero Data Retention"), so chained turns kept failing every other request after a brief recovery — the chain was reset but not disabled, so the next successful full-replay turn re-armed it. The ZDR phrasing is now classified categorically: one strike disables chaining for the session (skipping the three-strike circuit breaker) and the in-call retry drops `store: true`/`previous_response_id` and replays the full transcript instead ([#2341](https://github.com/can1357/oh-my-pi/issues/2341)).
44
+
5
45
  ## [15.11.4] - 2026-06-12
6
46
 
7
47
  ### Added
@@ -344,6 +344,9 @@ export declare const usageResponseSchema: z.ZodObject<{
344
344
  }>>;
345
345
  notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
346
346
  }, z.core.$strip>>;
347
+ resetCredits: z.ZodOptional<z.ZodObject<{
348
+ availableCount: z.ZodNumber;
349
+ }, z.core.$strip>>;
347
350
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
348
351
  raw: z.ZodOptional<z.ZodUnknown>;
349
352
  }, z.core.$strip>>;
@@ -11,7 +11,8 @@ import { Database } from "bun:sqlite";
11
11
  import type { ApiKeyResolver } from "./auth-retry";
12
12
  import type { OAuthController, OAuthCredentials, OAuthProviderId } from "./registry/oauth/types";
13
13
  import type { Provider } from "./types";
14
- import type { CredentialRankingStrategy, UsageLogger, UsageProvider, UsageReport } from "./usage";
14
+ import type { CredentialRankingStrategy, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage";
15
+ import { type CodexResetConsumeCode, type CodexResetCredit } from "./usage/openai-codex-reset";
15
16
  export type ApiKeyCredential = {
16
17
  type: "api_key";
17
18
  key: string;
@@ -225,6 +226,14 @@ export interface AuthCredentialStore {
225
226
  }): string | null;
226
227
  setCache(key: string, value: string, expiresAtSec: number): void;
227
228
  cleanExpiredCache(): void;
229
+ /**
230
+ * Append usage-limit snapshots for trend history. Optional: stores without
231
+ * durable storage (e.g. the broker remote store) omit it and recording is
232
+ * skipped — the broker host records into its own database instead.
233
+ */
234
+ recordUsageSnapshots?(entries: UsageHistoryEntry[]): void;
235
+ /** Read recorded usage-limit snapshots, oldest first. */
236
+ listUsageHistory?(query?: UsageHistoryQuery): UsageHistoryEntry[];
228
237
  /**
229
238
  * Optional store-supplied OAuth refresh. When present, `AuthStorage` uses
230
239
  * it before the per-provider local refresh path. `RemoteAuthCredentialStore`
@@ -450,6 +459,44 @@ export interface InvalidateCredentialMatchingOptions {
450
459
  signal?: AbortSignal;
451
460
  sessionId?: string;
452
461
  }
462
+ /**
463
+ * Identifies which stored account to redeem a saved rate-limit reset for.
464
+ * Any one field is enough; `credentialId` is the most precise.
465
+ */
466
+ export interface ResetCreditTarget {
467
+ credentialId?: number;
468
+ accountId?: string;
469
+ email?: string;
470
+ }
471
+ /** Outcome of {@link AuthStorage.redeemResetCredit}. */
472
+ export interface ResetCreditRedeemOutcome {
473
+ /** `true` only when a reset was actually applied (`code === "reset"`). */
474
+ ok: boolean;
475
+ /**
476
+ * Result code. Backend codes: `reset` (success), `already_redeemed`,
477
+ * `no_credit`, `nothing_to_reset`. Locally-synthesized: `no_account`
478
+ * (target not found), `account_unavailable` (token refresh failed),
479
+ * `http_<status>` (unexpected HTTP).
480
+ */
481
+ code: CodexResetConsumeCode;
482
+ accountId?: string;
483
+ email?: string;
484
+ /** The credit that was spent (when one was). */
485
+ creditId?: string;
486
+ }
487
+ /** One stored account's live saved-reset status, from {@link AuthStorage.listResetCredits}. */
488
+ export interface ResetCreditAccountStatus {
489
+ credentialId?: number;
490
+ accountId?: string;
491
+ email?: string;
492
+ /** Resets redeemable for this account right now (live, not cached). */
493
+ availableCount: number;
494
+ credits: CodexResetCredit[];
495
+ /** Whether this is the given session's active account. */
496
+ active: boolean;
497
+ /** Set when the account's token refresh or list call failed. */
498
+ error?: string;
499
+ }
453
500
  /**
454
501
  * Credential storage backed by an AuthCredentialStore.
455
502
  * Reads from storage on reload(), manages round-robin credential selection,
@@ -621,6 +668,11 @@ export declare class AuthStorage {
621
668
  * Logout from a provider.
622
669
  */
623
670
  logout(provider: string): Promise<void>;
671
+ /**
672
+ * Recorded usage-limit snapshots, oldest first. Empty when the underlying
673
+ * store has no durable history (e.g. a broker-backed remote store).
674
+ */
675
+ listUsageHistory(query?: UsageHistoryQuery): UsageHistoryEntry[];
624
676
  ingestUsageHeaders(provider: Provider, headers: Record<string, string>, options?: {
625
677
  sessionId?: string;
626
678
  baseUrl?: string;
@@ -713,6 +765,38 @@ export declare class AuthStorage {
713
765
  * exercise each stored account exactly once.
714
766
  */
715
767
  getOAuthAccesses(provider: string, options?: AuthApiKeyOptions): Promise<OAuthAccessResolution[]>;
768
+ /**
769
+ * List saved rate-limit resets for every stored OAuth account of `provider`
770
+ * (Codex), fetched LIVE from the dedicated `rate-limit-reset-credits` route.
771
+ *
772
+ * This deliberately bypasses the usage-report cache: `/wham/usage` is
773
+ * IP-rate-limited and may serve stale (or pre-feature) snapshots when many
774
+ * accounts are polled, which would hide redeemable credits. One entry per
775
+ * account, with the session's active account flagged and unreachable
776
+ * accounts carrying an `error`.
777
+ */
778
+ listResetCredits(options?: {
779
+ provider?: string;
780
+ sessionId?: string;
781
+ baseUrlResolver?: (provider: string) => string | undefined;
782
+ signal?: AbortSignal;
783
+ }): Promise<ResetCreditAccountStatus[]>;
784
+ /**
785
+ * Redeem one saved rate-limit reset (OpenAI Codex "saved resets") for a
786
+ * specific stored account.
787
+ *
788
+ * Resolves a fresh access token for the target account, picks an available
789
+ * credit (the given `creditId`, else the first redeemable one), spends it,
790
+ * and invalidates the cached usage report so the next `/usage` reflects the
791
+ * reset. Never throws for business outcomes — inspect the returned `code`.
792
+ */
793
+ redeemResetCredit(options: {
794
+ target: ResetCreditTarget;
795
+ provider?: string;
796
+ creditId?: string;
797
+ baseUrlResolver?: (provider: string) => string | undefined;
798
+ signal?: AbortSignal;
799
+ }): Promise<ResetCreditRedeemOutcome>;
716
800
  invalidateCredentialMatching(provider: string, apiKey: string, options?: InvalidateCredentialMatchingOptions): Promise<boolean>;
717
801
  invalidateCredentialMatching(provider: string, apiKey: string, signal?: AbortSignal): Promise<boolean>;
718
802
  /**
@@ -836,6 +920,8 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
836
920
  }): string | null;
837
921
  setCache(key: string, value: string, expiresAtSec: number): void;
838
922
  cleanExpiredCache(): void;
923
+ recordUsageSnapshots(entries: UsageHistoryEntry[]): void;
924
+ listUsageHistory(query?: UsageHistoryQuery): UsageHistoryEntry[];
839
925
  /**
840
926
  * Save OAuth credentials for a provider.
841
927
  * Preserves unrelated identities and replaces only the matching credential.
@@ -34,6 +34,7 @@ export * from "./usage/google-antigravity";
34
34
  export * from "./usage/kimi";
35
35
  export * from "./usage/minimax-code";
36
36
  export * from "./usage/openai-codex";
37
+ export * from "./usage/openai-codex-reset";
37
38
  export * from "./usage/zai";
38
39
  export * from "./utils/anthropic-auth";
39
40
  export * from "./utils/event-stream";
@@ -83,6 +83,11 @@ export interface AnthropicOptions extends StreamOptions {
83
83
  * Ignored for adaptive-capable models.
84
84
  */
85
85
  thinkingBudgetTokens?: number;
86
+ /**
87
+ * Upstream wire model id override for collapsed effort-tier variants.
88
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
89
+ */
90
+ requestModelId?: string;
86
91
  /**
87
92
  * Effort level for adaptive thinking.
88
93
  * Controls how much thinking Claude allocates:
@@ -34,7 +34,23 @@ export interface GoogleGeminiCliOptions extends StreamOptions {
34
34
  budgetTokens?: number;
35
35
  /** Thinking level. Use for Gemini 3 models (LOW/HIGH for Pro, MINIMAL/LOW/MEDIUM/HIGH for Flash). */
36
36
  level?: GoogleThinkingLevel;
37
+ /**
38
+ * Explicit wire suppression when `enabled` is false. Cloud Code Assist
39
+ * re-applies the per-id baked server default when thinkingConfig is
40
+ * omitted, so models with `thinking.suppressWhenOff` must send
41
+ * `includeThoughts: false` plus a MINIMAL level (or zero budget).
42
+ */
43
+ suppress?: {
44
+ level: GoogleThinkingLevel;
45
+ } | {
46
+ budget: number;
47
+ };
37
48
  };
49
+ /**
50
+ * Upstream wire model id override for collapsed effort-tier variants.
51
+ * Serialized as `requestModelId ?? model.requestModelId ?? model.id`.
52
+ */
53
+ requestModelId?: string;
38
54
  projectId?: string;
39
55
  }
40
56
  export { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders, getGeminiCliUserAgent, } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
@@ -0,0 +1,79 @@
1
+ /**
2
+ * OpenAI Codex "saved rate limit reset" redemption client.
3
+ *
4
+ * OpenAI lets paid Codex accounts bank a usage-window reset and spend it on
5
+ * demand (announced 2026-06-11). The count is surfaced on `/wham/usage` as
6
+ * `rate_limit_reset_credits.available_count` (see `./openai-codex.ts`), but the
7
+ * actual credit objects and the redeem action live on two dedicated routes:
8
+ *
9
+ * GET /wham/rate-limit-reset-credits → list redeemable credits
10
+ * POST /wham/rate-limit-reset-credits/consume → spend one credit
11
+ * body: { credit_id, redeem_request_id }
12
+ *
13
+ * `redeem_request_id` is a client-generated idempotency key (UUID). The consume
14
+ * response carries a `code`: `"reset"` on success, otherwise a business reason
15
+ * (`already_redeemed`, `no_credit`, `nothing_to_reset`).
16
+ *
17
+ * These are thin, dependency-light functions so both the interactive session
18
+ * (the `/usage reset` command + auto-redeem) and any out-of-band tooling can
19
+ * share one wire contract.
20
+ */
21
+ import type { FetchImpl } from "../types";
22
+ /** A single redeemable (or already-spent) saved reset. */
23
+ export interface CodexResetCredit {
24
+ /** Opaque credit id, e.g. `RateLimitResetCredit_…`. Pass to {@link consumeCodexResetCredit}. */
25
+ id: string;
26
+ /** Backend reset family, e.g. `codex_rate_limits`. */
27
+ resetType?: string;
28
+ /** `available`, `redeemed`, … */
29
+ status?: string;
30
+ grantedAt?: string;
31
+ expiresAt?: string;
32
+ redeemStartedAt?: string | null;
33
+ redeemedAt?: string | null;
34
+ /** Human-facing card title, e.g. "One free rate limit reset". */
35
+ title?: string;
36
+ description?: string;
37
+ }
38
+ /** Result of listing an account's saved resets. */
39
+ export interface CodexResetCreditList {
40
+ credits: CodexResetCredit[];
41
+ /** Backend-reported count of credits redeemable right now. */
42
+ availableCount: number;
43
+ }
44
+ /**
45
+ * Consume outcome `code`. `reset` means a window was actually reset; the others
46
+ * are no-op business outcomes the caller should surface verbatim-ish to the user.
47
+ */
48
+ export type CodexResetConsumeCode = "reset" | "already_redeemed" | "no_credit" | "nothing_to_reset" | (string & {});
49
+ export interface CodexResetConsumeResult {
50
+ /** `true` only when `code === "reset"` (a reset was applied). */
51
+ ok: boolean;
52
+ code: CodexResetConsumeCode;
53
+ /** HTTP status of the consume call (for diagnostics). */
54
+ status: number;
55
+ raw?: unknown;
56
+ }
57
+ interface CodexResetAuth {
58
+ accessToken: string;
59
+ accountId?: string;
60
+ /** Provider base URL override; defaults to the Codex backend. */
61
+ baseUrl?: string;
62
+ fetch: FetchImpl;
63
+ signal?: AbortSignal;
64
+ }
65
+ /**
66
+ * List the account's saved rate-limit resets. Returns `null` on transport/auth
67
+ * failure (non-2xx or thrown), letting callers treat it the same as "no data".
68
+ */
69
+ export declare function listCodexResetCredits(auth: CodexResetAuth): Promise<CodexResetCreditList | null>;
70
+ /**
71
+ * Spend one saved reset. `redeemRequestId` is the idempotency key; one is
72
+ * generated when omitted, so retrying with the SAME id is safe and won't
73
+ * double-spend. The returned `code` is `"reset"` on success.
74
+ */
75
+ export declare function consumeCodexResetCredit(auth: CodexResetAuth & {
76
+ creditId: string;
77
+ redeemRequestId?: string;
78
+ }): Promise<CodexResetConsumeResult>;
79
+ export {};
@@ -1,3 +1,4 @@
1
1
  import type { CredentialRankingStrategy, UsageProvider } from "../usage";
2
+ export declare function normalizeCodexBaseUrl(baseUrl?: string): string;
2
3
  export declare const openaiCodexUsageProvider: UsageProvider;
3
4
  export declare const codexRankingStrategy: CredentialRankingStrategy;
@@ -57,14 +57,64 @@ export interface UsageLimit {
57
57
  status?: UsageStatus;
58
58
  notes?: string[];
59
59
  }
60
+ /**
61
+ * Saved/banked rate-limit resets an account can redeem on demand.
62
+ *
63
+ * Surfaced by providers that let users defer a usage-window reset and spend it
64
+ * later (OpenAI Codex "saved rate limit resets"). The redeem itself is a
65
+ * separate, provider-specific action; this is the read-only count for display.
66
+ */
67
+ export interface UsageResetCredits {
68
+ /** Number of resets available to redeem right now. */
69
+ availableCount: number;
70
+ }
60
71
  /** Aggregated usage report for a provider. */
61
72
  export interface UsageReport {
62
73
  provider: Provider;
63
74
  fetchedAt: number;
64
75
  limits: UsageLimit[];
76
+ /** Saved rate-limit resets the account can redeem, when the provider reports them. */
77
+ resetCredits?: UsageResetCredits;
65
78
  metadata?: Record<string, unknown>;
66
79
  raw?: unknown;
67
80
  }
81
+ /**
82
+ * Resolve a limit's used fraction (0..1; >1 means overage) from whichever
83
+ * amount fields the provider populated. Precedence mirrors the usage UIs:
84
+ * explicit fraction > used/limit > percent-unit used > inverted remaining.
85
+ */
86
+ export declare function resolveUsedFraction(limit: UsageLimit): number | undefined;
87
+ /**
88
+ * One recorded usage-limit snapshot: a single limit window of one account at
89
+ * a point in time. The usage cache itself is latest-snapshot-only; history
90
+ * rows are appended by the auth storage layer whenever a fresh report is
91
+ * fetched, so limit utilization stays inspectable over time.
92
+ */
93
+ export interface UsageHistoryEntry {
94
+ /** Epoch ms the report was fetched. */
95
+ recordedAt: number;
96
+ provider: Provider;
97
+ /** Stable credential identity key (account/email/project derived). */
98
+ accountKey: string;
99
+ email?: string;
100
+ accountId?: string;
101
+ /** {@link UsageLimit.id} of the recorded window. */
102
+ limitId: string;
103
+ /** Human label of the limit. */
104
+ label: string;
105
+ windowLabel?: string;
106
+ /** Used fraction (0..1) when resolvable. */
107
+ usedFraction?: number;
108
+ status?: UsageStatus;
109
+ /** Epoch ms the window resets, when known. */
110
+ resetsAt?: number;
111
+ }
112
+ /** Filter for reading recorded usage history. */
113
+ export interface UsageHistoryQuery {
114
+ provider?: string;
115
+ /** Inclusive lower bound on {@link UsageHistoryEntry.recordedAt} (epoch ms). */
116
+ sinceMs?: number;
117
+ }
68
118
  export declare const usageUnitSchema: z.ZodEnum<{
69
119
  bytes: "bytes";
70
120
  minutes: "minutes";
@@ -155,6 +205,9 @@ export declare const usageLimitSchema: z.ZodObject<{
155
205
  }>>;
156
206
  notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
157
207
  }, z.core.$strip>;
208
+ export declare const usageResetCreditsSchema: z.ZodObject<{
209
+ availableCount: z.ZodNumber;
210
+ }, z.core.$strip>;
158
211
  export declare const usageReportSchema: z.ZodObject<{
159
212
  provider: z.ZodString;
160
213
  fetchedAt: z.ZodNumber;
@@ -201,6 +254,9 @@ export declare const usageReportSchema: z.ZodObject<{
201
254
  }>>;
202
255
  notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
203
256
  }, z.core.$strip>>;
257
+ resetCredits: z.ZodOptional<z.ZodObject<{
258
+ availableCount: z.ZodNumber;
259
+ }, z.core.$strip>>;
204
260
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
205
261
  raw: z.ZodOptional<z.ZodUnknown>;
206
262
  }, z.core.$strip>;
@@ -22,6 +22,9 @@ export interface RequestDebugSession {
22
22
  wrapResponse(response: Response): Promise<Response>;
23
23
  }
24
24
  export declare function isRequestDebugEnabled(): boolean;
25
+ export declare function setNextRequestDebugPath(requestPath: string): void;
26
+ export declare function clearNextRequestDebugPath(): void;
27
+ export declare function getNextRequestDebugPath(): string | undefined;
25
28
  export declare function wrapFetchForRequestDebug(fetchImpl: FetchImpl): FetchImpl;
26
29
  export declare function withRequestDebugFetch<T extends {
27
30
  fetch?: FetchImpl;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "15.11.4",
4
+ "version": "15.11.7",
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,8 +38,8 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "@oh-my-pi/pi-catalog": "15.11.4",
42
- "@oh-my-pi/pi-utils": "15.11.4",
41
+ "@oh-my-pi/pi-catalog": "15.11.7",
42
+ "@oh-my-pi/pi-utils": "15.11.7",
43
43
  "openai": "^6.39.0",
44
44
  "partial-json": "^0.1.7",
45
45
  "zod": "4.4.3"