@oh-my-pi/pi-catalog 18.8.4 → 18.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.8.5] - 2026-10-08
6
+
7
+ ### Breaking Changes
8
+
9
+ - `googleAntigravityModelManagerOptions` takes `resolveAccounts` instead of `oauthToken`, and `fetchAntigravityDiscoveryModels` returns a roster or credential-rejection result instead of a bare list ([#14924](https://github.com/can1357/oh-my-pi/issues/14924)).
10
+
11
+ ### Fixed
12
+
13
+ - Fixed Claude Haiku 5.5 on Cursor showing as unpriced; it now uses Cursor's $0.10/$0.50 rate card and 5x long-context tier above 100K input tokens ([#14890](https://github.com/can1357/oh-my-pi/pull/14890) by [@eggpeat](https://github.com/eggpeat)).
14
+ - Fixed Claude Haiku 5.5 on GitHub Copilot pricing cache reads and writes at $0 on both the standard and `-1m` long-context rows, and the `-1m` row billing the 5x long-context band a second time above 100K input tokens ([#14890](https://github.com/can1357/oh-my-pi/pull/14890) by [@eggpeat](https://github.com/eggpeat)).
15
+
5
16
  ## [18.8.4] - 2026-10-08
6
17
 
7
18
  ### Fixed
@@ -63,13 +63,23 @@ export interface FetchAntigravityDiscoveryModelsOptions {
63
63
  */
64
64
  collapseTable?: VariantCollapseTable;
65
65
  }
66
+ /**
67
+ * A complete account roster, or a definitive 401/403 credential rejection.
68
+ * `null` from {@link fetchAntigravityDiscoveryModels} instead means a transient
69
+ * failure or invalid response, which must not replace an authoritative catalog.
70
+ */
71
+ export interface AntigravityModelDiscoveryResult {
72
+ models: ModelSpec<"google-gemini-cli">[];
73
+ rejectedStatus?: 401 | 403;
74
+ }
66
75
  /**
67
76
  * Fetches discoverable Antigravity models and normalizes them into canonical model entries.
68
77
  *
69
- * Returns `null` on network/payload/auth failures.
70
- * Returns `[]` only when the endpoint responds successfully with no usable models.
78
+ * Returns `null` on network, server, or payload failures; a 401/403 is reported
79
+ * separately so multi-account discovery can skip a rejected credential.
80
+ * A successful empty roster has `models: []` without `rejectedStatus`.
71
81
  */
72
- export declare function fetchAntigravityDiscoveryModels(options: FetchAntigravityDiscoveryModelsOptions): Promise<ModelSpec<"google-gemini-cli">[] | null>;
82
+ export declare function fetchAntigravityDiscoveryModels(options: FetchAntigravityDiscoveryModelsOptions): Promise<AntigravityModelDiscoveryResult | null>;
73
83
  /** Advertised image model and serving endpoint for one Antigravity account. */
74
84
  export interface AntigravityImageModel {
75
85
  id: string;
@@ -0,0 +1,11 @@
1
+ import type { Api, ModelSpec } from "../types.js";
2
+ /**
3
+ * Merge per-account discovery catalogs into one list, deduped by model id.
4
+ *
5
+ * The first catalog to list an id supplies its spec; {@link ModelSpec.accountAccess}
6
+ * is merged from every catalog listing that id, so credential selection can
7
+ * route account-gated models to the accounts that serve them. Used by
8
+ * multi-account discovery for Codex and Antigravity, whose rosters differ per
9
+ * account.
10
+ */
11
+ export declare function unionAccountCatalogs<TApi extends Api>(catalogs: readonly (readonly ModelSpec<TApi>[])[]): ModelSpec<TApi>[];
@@ -11,8 +11,30 @@ export interface GoogleVertexModelManagerConfig {
11
11
  signal?: AbortSignal;
12
12
  fetch?: FetchImpl;
13
13
  }
14
+ /** One Antigravity OAuth account whose `fetchAvailableModels` roster discovery reads. */
15
+ export interface GoogleAntigravityAccount {
16
+ /** OAuth access token used for `Authorization: Bearer ...`. */
17
+ accessToken: string;
18
+ /**
19
+ * Credential identity recorded in {@link ModelSpec.accountAccess} for every
20
+ * model this account serves (see `oauthAccountKey` in `@oh-my-pi/pi-ai`).
21
+ * When any account lacks one, no model records per-account access.
22
+ */
23
+ accountKey?: string;
24
+ }
14
25
  export interface GoogleAntigravityModelManagerConfig {
15
- oauthToken?: string;
26
+ /**
27
+ * Resolves every configured Antigravity account at discovery time. Rosters
28
+ * are plan-scoped (Claude 5.5 is served to some accounts only) and
29
+ * authoritative, so each account's roster is fetched and the results are
30
+ * unioned; reading one account's roster would prune models only its
31
+ * siblings serve (#14924).
32
+ *
33
+ * Returns `null` to abort discovery (e.g. an account's credential failed to
34
+ * refresh), keeping the previous/bundled catalog instead of caching a
35
+ * partial one.
36
+ */
37
+ resolveAccounts?: () => Promise<readonly GoogleAntigravityAccount[] | null>;
16
38
  endpoint?: string;
17
39
  fetch?: FetchImpl;
18
40
  }
@@ -1209,11 +1209,11 @@ export interface Model<TApi extends Api = Api> {
1209
1209
  cursorMaxModeRoutes?: Readonly<Record<string, boolean>>;
1210
1210
  /**
1211
1211
  * Per-account availability recorded by multi-account discovery: provider
1212
- * account id (Codex: ChatGPT `chatgpt_account_id`) → that account's
1213
- * entitlements on this model. An account appears only when its own catalog
1214
- * lists the model, so credential selection can route account-gated models
1215
- * (e.g. `gpt-daybreak-blue-latest`) straight to eligible accounts. Absent on
1216
- * bundled/config rows and on single-account discovery.
1212
+ * account key (Codex: ChatGPT `chatgpt_account_id`; Antigravity: login
1213
+ * email) → that account's entitlements on this model. An account appears
1214
+ * only when its own catalog lists the model, so credential selection can
1215
+ * route account-gated models (e.g. `gpt-daybreak-blue-latest`, Antigravity
1216
+ * Claude 5.5) straight to eligible accounts. Absent on bundled/config rows.
1217
1217
  */
1218
1218
  accountAccess?: Readonly<Record<string, ModelAccountAccess>>;
1219
1219
  /** Cursor `RequestedModel.parameters` for this model's default variant. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-catalog",
3
- "version": "18.8.4",
3
+ "version": "18.8.5",
4
4
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
5
5
  "keywords": [
6
6
  "ai",
@@ -93,12 +93,12 @@
93
93
  "gen:proto": "bun scripts/generate-protocols.ts"
94
94
  },
95
95
  "dependencies": {
96
- "@oh-my-pi/omptype": "18.8.4",
97
- "@oh-my-pi/pi-utils": "18.8.4"
96
+ "@oh-my-pi/omptype": "18.8.5",
97
+ "@oh-my-pi/pi-utils": "18.8.5"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@bgotink/kdl": "0.4.0",
101
- "@oh-my-pi/pi-ai": "18.8.4",
101
+ "@oh-my-pi/pi-ai": "18.8.5",
102
102
  "@types/bun": "^1.3.14"
103
103
  },
104
104
  "engines": {
package/src/build.ts CHANGED
@@ -176,40 +176,6 @@ export function applyCatalogCorrections(
176
176
  model: Pick<ModelSpec<Api>, "cost" | "contextWindow" | "maxTokens" | "input">,
177
177
  catalog: Record<string, unknown>,
178
178
  ): void {
179
- const longContext = objectPayload(catalog.longContext);
180
- if (longContext !== undefined) {
181
- const inputThreshold = numberField(longContext, "inputThreshold");
182
- const inclusive = Reflect.get(longContext, "inputThresholdInclusive") === true;
183
- const multiplier = numberField(longContext, "multiplier");
184
- const input = numberField(longContext, "input");
185
- const output = numberField(longContext, "output");
186
- const cacheRead = numberField(longContext, "cacheRead");
187
- const cacheWrite = numberField(longContext, "cacheWrite");
188
- const base = model.cost;
189
- const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
190
- if (inputThreshold !== undefined && multiplier !== undefined && hasTokenPrice) {
191
- // Multiplier form: tier rates derive from the row's live list price.
192
- model.cost = {
193
- ...base,
194
- longContext: {
195
- inputThreshold,
196
- ...(inclusive && { inputThresholdInclusive: true }),
197
- input: base.input * multiplier,
198
- output: base.output * multiplier,
199
- cacheRead: base.cacheRead * multiplier,
200
- cacheWrite: base.cacheWrite * multiplier,
201
- },
202
- };
203
- } else if (
204
- inputThreshold !== undefined &&
205
- input !== undefined &&
206
- output !== undefined &&
207
- cacheRead !== undefined &&
208
- cacheWrite !== undefined
209
- ) {
210
- model.cost = { ...model.cost, longContext: { inputThreshold, input, output, cacheRead, cacheWrite } };
211
- }
212
- }
213
179
  const patch = objectPayload(catalog.costPatch);
214
180
  if (patch !== undefined) {
215
181
  model.cost = { ...model.cost };
@@ -246,6 +212,41 @@ export function applyCatalogCorrections(
246
212
  applyEffectiveFallbackRates(model.cost, Reflect.get(fallback, "effectiveRates"));
247
213
  }
248
214
  }
215
+ const longContext = objectPayload(catalog.longContext);
216
+ if (longContext !== undefined) {
217
+ const inputThreshold = numberField(longContext, "inputThreshold");
218
+ const inclusive = Reflect.get(longContext, "inputThresholdInclusive") === true;
219
+ const multiplier = numberField(longContext, "multiplier");
220
+ const input = numberField(longContext, "input");
221
+ const output = numberField(longContext, "output");
222
+ const cacheRead = numberField(longContext, "cacheRead");
223
+ const cacheWrite = numberField(longContext, "cacheWrite");
224
+ const base = model.cost;
225
+ const hasTokenPrice = base.input !== 0 || base.output !== 0 || base.cacheRead !== 0 || base.cacheWrite !== 0;
226
+ if (inputThreshold !== undefined && multiplier !== undefined && hasTokenPrice) {
227
+ // Multiplier form: tier rates derive from the row's final base card,
228
+ // after `cost-patch`/`cost-fallback` corrected the live list price.
229
+ model.cost = {
230
+ ...base,
231
+ longContext: {
232
+ inputThreshold,
233
+ ...(inclusive && { inputThresholdInclusive: true }),
234
+ input: base.input * multiplier,
235
+ output: base.output * multiplier,
236
+ cacheRead: base.cacheRead * multiplier,
237
+ cacheWrite: base.cacheWrite * multiplier,
238
+ },
239
+ };
240
+ } else if (
241
+ inputThreshold !== undefined &&
242
+ input !== undefined &&
243
+ output !== undefined &&
244
+ cacheRead !== undefined &&
245
+ cacheWrite !== undefined
246
+ ) {
247
+ model.cost = { ...model.cost, longContext: { inputThreshold, input, output, cacheRead, cacheWrite } };
248
+ }
249
+ }
249
250
  if (catalog.cacheReadAtInputRate === true) {
250
251
  const { longContext } = model.cost;
251
252
  model.cost = {
@@ -213,7 +213,7 @@ The three value shapes are:
213
213
  - **Object**: no arguments and a child block, including an empty block. Child names are kebab-case: an axis-directive spelling compiles to its resolved axis key (`template-reasoning-effort` → `qwenTemplateReasoningEffort`), anything else converts mechanically (`input-threshold` → `inputThreshold`); camelCase names are a compile error. `extra-body` payloads (top-level or nested) are the exception — their child names are literal wire JSON keys copied verbatim (`enable_thinking`). Each child is either one scalar or another object; arrays are not representable inside an object payload.
214
214
 
215
215
  A rule cannot assign the same resolved axis twice in one block.
216
- One object axis carries a computed form: `long-context-cost` accepts either the absolute rates (`input-threshold` + `input`/`output`/`cache-read`/`cache-write`) or `input-threshold` + `multiplier` (with optional `input-threshold-inclusive`), which derives the tier from the row's live base price at build time so the rule tracks upstream list-price updates (xAI's SuperGrok 200K tier). Rows without a token price carry no tier.
216
+ One object axis carries a computed form: `long-context-cost` accepts either the absolute rates (`input-threshold` + `input`/`output`/`cache-read`/`cache-write`) or `input-threshold` + `multiplier` (with optional `input-threshold-inclusive`), which derives the tier from the row's base price at build time, after `cost-patch` and `cost-fallback`, so the rule tracks upstream list-price updates (xAI's SuperGrok 200K tier). Rows without a token price carry no tier. An empty `long-context-cost {}` adds no tier; a more specific rule uses it to opt a host out of a broader rule's band (GitHub Copilot's Haiku 5.5 rows, whose long tier is the `-1m` sibling).
217
217
 
218
218
  `context-window-authoritative #true` preserves a host's supplied context window through runtime model selection instead of applying inferred expansion or reference-price-tier caps. Explicit user context overrides still apply afterward. It applies to rows materialized through `buildModel` (discovery and regenerated bundles). See [`providers/factory-droid.kdl`](providers/factory-droid.kdl).
219
219
 
@@ -102,7 +102,8 @@ class "anthropic" {
102
102
  }
103
103
  // Haiku 5.5 bills every rate 5x for prompts over 100K. Derived from each
104
104
  // host's list price so non-Anthropic hosts get the band too; the Claude
105
- // API pins absolute rates further down.
105
+ // API and Cursor pin absolute rates, and GitHub Copilot opts out because
106
+ // its long tier is the `-1m` sibling row.
106
107
  // https://platform.claude.com/docs/en/models/haiku-5-5/overview#pricing
107
108
  revision ">=5.5 <6" {
108
109
  long-context-cost {
@@ -167,6 +167,24 @@ provider "cursor" {
167
167
  cache-write 12.5
168
168
  }
169
169
  }
170
+ // Claude Haiku 5.5: $0.10/$0.50, cache $0.01/$0.125. Requests above
171
+ // 100k input tokens bill every leg at 5x ($0.50/$2.50, cache
172
+ // $0.05/$0.625), on the 1M lane too.
173
+ models "claude-haiku-5-5" "claude-haiku-5-5-1m" {
174
+ cost-patch {
175
+ input 0.1
176
+ output 0.5
177
+ cache-read 0.01
178
+ cache-write 0.125
179
+ }
180
+ long-context-cost {
181
+ input-threshold 100000
182
+ input 0.5
183
+ output 2.5
184
+ cache-read 0.05
185
+ cache-write 0.625
186
+ }
187
+ }
170
188
  // Claude 4.7 Opus: $5/$25, cache $0.50/$6.25. Up to 1M same rates.
171
189
  models "claude-opus-4-7-high" "claude-opus-4-7-low" "claude-opus-4-7-max" "claude-opus-4-7-medium" "claude-opus-4-7-xhigh" {
172
190
  cost-patch {
@@ -76,4 +76,29 @@ provider "github-copilot" {
76
76
  }
77
77
  max-context-window 1050000
78
78
  }
79
+ // Claude Haiku 5.5: `billing.token_prices` reports its input/output rates
80
+ // but no cache legs, so discovered rows priced cache reads and writes at
81
+ // $0. Copilot's rate card bills $0.01 read / $0.125 write up to 100K and
82
+ // $0.05 / $0.625 on the long tier (the `-1m` sibling).
83
+ // https://docs.github.com/en/copilot/reference/copilot-billing/models-and-pricing
84
+ // Copilot serves that tier as the `-1m` row (promotion target of the base
85
+ // row), so both rows opt out of the class-wide 100K 5x band: the base row
86
+ // would otherwise bill the band on top of the promotion, and the `-1m`
87
+ // row would charge 5x twice.
88
+ models "claude-haiku-5.5" {
89
+ cost-patch {
90
+ cache-read 0.01
91
+ cache-write 0.125
92
+ }
93
+ long-context-cost {
94
+ }
95
+ }
96
+ models "claude-haiku-5.5-1m" {
97
+ cost-patch {
98
+ cache-read 0.05
99
+ cache-write 0.625
100
+ }
101
+ long-context-cost {
102
+ }
103
+ }
79
104
  }