@graphorin/pricing 0.5.0 → 0.6.1

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
@@ -1,5 +1,44 @@
1
1
  # @graphorin/pricing
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
8
+
9
+ - Updated dependencies [[`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
10
+ - @graphorin/core@0.6.1
11
+
12
+ ## 0.6.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Prompt-cache economics end-to-end (audit 2026-07-04 Wave C, cluster C1).
17
+
18
+ - `Usage` gains `cachedReadTokens` / `cacheWriteTokens` (subsets of `promptTokens`), mapped by the vercel adapter (v7 `inputTokenDetails`; reasoning split kept exclusive of `completionTokens`) and the OpenAI-compatible adapter (`prompt_tokens_details.cached_tokens`); the fields flow through step/run aggregation, `usageByModel`, run-state (de)serialization and `withCostTracking` (new `cachedReadPerMtok`/`cacheWritePerMtok` lookup rates, full-input-rate fallback).
19
+ - New opt-in `ProviderRequest.cachePolicy` / `AgentConfig.cachePolicy` (`{ breakpoints: 'auto', ttl? }`): the vercel adapter anchors Anthropic `cache_control` on the first and last conversation messages so the stable prefix is written once and read at the discounted rate every later step.
20
+ - `ModelPrice` gains `cacheWriteUsdPerToken`; `calculateCost` bills cache writes; the bundled pricing snapshot is regenerated (2026-07-04) with current Anthropic 4.x / OpenAI gpt-5 + gpt-4.1 + o3/o4-mini / Gemini 2.5 families (legacy ids retained), `lookupPrice` resolves dated ids via a date-suffix fallback, and a new snapshot-coverage release gate cross-checks the model-tier classifier against the snapshot with an explicit known-unpriced allowlist (post-cutoff models report null cost + WARN instead of invented numbers).
21
+ - Cache-friendly catalogue: handoff tools serialize BEFORE the growing promoted section (byte-stable prefix under `tool_search` promotions), and new `toolPromotion: 'run-boundary'` freezes the advertised catalogue for a whole run while still persisting discoveries.
22
+
23
+ ### Patch Changes
24
+
25
+ - [#138](https://github.com/o-stepper/graphorin/pull/138) [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a) Thanks [@o-stepper](https://github.com/o-stepper)! - OTel GenAI alignment + honest eval statistics (audit 2026-07-04 Wave E, cluster E8).
26
+
27
+ - Span names follow the GenAI semconv `{operation} {target}` shape when attributes carry the target (`chat <model>`, `execute_tool <tool>`, `invoke_agent <agent>`); the operation-mapping table and `GenAIOperationName` gain `invoke_agent` to match what the runtime actually emits; `agent.step` spans carry `gen_ai.agent.name`; pricing's `listMissingModels` reads the current `gen_ai.provider.name` attribute first (deprecated `gen_ai.system` kept as fallback).
28
+ - New `@graphorin/evals` stats module: `mean`/`sampleStddev`, Wilson 95% interval, `passHatK` over `-iter-N` outcomes, and McNemar paired significance. `runEvals` summaries always carry `passRateCi` and (under `iterations > 1`) `passHatK`; `detectRegressions` annotates pass-rate-drop findings with the paired regressed/improved counts + p-value, and opt-in `requireSignificance` vetoes drops the paired test cannot distinguish from noise - the fixed tolerance alone was sample-size blind.
29
+
30
+ - Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627)]:
31
+ - @graphorin/core@0.6.0
32
+
33
+ ## 0.5.0
34
+
35
+ First version published to the npm registry (with Sigstore build
36
+ provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
37
+ milestones and were never published. All `@graphorin/*` packages release
38
+ lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
39
+ in the repository-level
40
+ [CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
41
+
3
42
  ## 0.1.0
4
43
 
5
44
  Initial release. See the workspace root `CHANGELOG.md` for the full
package/README.md CHANGED
@@ -66,4 +66,4 @@ MIT © 2026 [Oleksiy Stepurenko](https://github.com/o-stepper).
66
66
 
67
67
  ---
68
68
 
69
- **Project Graphorin** · v0.5.0 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
69
+ **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","names":[],"sources":["../src/diff.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;iBA0BgB,WAAA,SACN,wBACD,kBACN,cAAc"}
1
+ {"version":3,"file":"diff.d.ts","names":[],"sources":["../src/diff.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;iBA2BgB,WAAA,SACN,wBACD,kBACN,cAAc"}
package/dist/diff.js CHANGED
@@ -3,6 +3,7 @@ const COMPARED_FIELDS = [
3
3
  "inputUsdPerToken",
4
4
  "outputUsdPerToken",
5
5
  "cachedReadUsdPerToken",
6
+ "cacheWriteUsdPerToken",
6
7
  "reasoningUsdPerToken",
7
8
  "region",
8
9
  "notes"
package/dist/diff.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","names":["COMPARED_FIELDS: ReadonlyArray<keyof ModelPrice>","out: PricingDiffEntry[]"],"sources":["../src/diff.ts"],"sourcesContent":["/**\n * `diffPricing(...)` produce a row-by-row delta between two pricing\n * snapshots. Used by the upcoming `graphorin pricing diff` CLI to\n * surface upstream changes after a `refresh`.\n *\n * @packageDocumentation\n */\n\nimport type { ModelPrice, PricingDiffEntry, PricingSnapshot } from './types.js';\n\nconst COMPARED_FIELDS: ReadonlyArray<keyof ModelPrice> = [\n 'inputUsdPerToken',\n 'outputUsdPerToken',\n 'cachedReadUsdPerToken',\n 'reasoningUsdPerToken',\n 'region',\n 'notes',\n];\n\n/**\n * Compare two snapshots and return one entry per (provider, model)\n * pair that has been added, removed, or changed. The result is sorted\n * by `(provider, model, kind)` for deterministic output.\n *\n * @stable\n */\nexport function diffPricing(\n before: PricingSnapshot,\n after: PricingSnapshot,\n): ReadonlyArray<PricingDiffEntry> {\n const beforeMap = indexBy(before.entries);\n const afterMap = indexBy(after.entries);\n const out: PricingDiffEntry[] = [];\n\n for (const [key, entry] of beforeMap) {\n const next = afterMap.get(key);\n if (next === undefined) {\n out.push({ provider: entry.provider, model: entry.model, kind: 'removed', before: entry });\n continue;\n }\n const changedFields = COMPARED_FIELDS.filter((field) => entry[field] !== next[field]);\n if (changedFields.length > 0) {\n out.push({\n provider: entry.provider,\n model: entry.model,\n kind: 'changed',\n before: entry,\n after: next,\n changedFields,\n });\n }\n }\n\n for (const [key, entry] of afterMap) {\n if (beforeMap.has(key)) continue;\n out.push({ provider: entry.provider, model: entry.model, kind: 'added', after: entry });\n }\n\n return out.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider < b.provider ? -1 : 1;\n if (a.model !== b.model) return a.model < b.model ? -1 : 1;\n return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;\n });\n}\n\nfunction indexBy(entries: ReadonlyArray<ModelPrice>): Map<string, ModelPrice> {\n const out = new Map<string, ModelPrice>();\n for (const entry of entries) {\n out.set(`${entry.provider}/${entry.model}/${entry.region ?? ''}`, entry);\n }\n return out;\n}\n"],"mappings":";AAUA,MAAMA,kBAAmD;CACvD;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,YACd,QACA,OACiC;CACjC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,WAAW,QAAQ,MAAM,QAAQ;CACvC,MAAMC,MAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,KAAK,UAAU,WAAW;EACpC,MAAM,OAAO,SAAS,IAAI,IAAI;AAC9B,MAAI,SAAS,QAAW;AACtB,OAAI,KAAK;IAAE,UAAU,MAAM;IAAU,OAAO,MAAM;IAAO,MAAM;IAAW,QAAQ;IAAO,CAAC;AAC1F;;EAEF,MAAM,gBAAgB,gBAAgB,QAAQ,UAAU,MAAM,WAAW,KAAK,OAAO;AACrF,MAAI,cAAc,SAAS,EACzB,KAAI,KAAK;GACP,UAAU,MAAM;GAChB,OAAO,MAAM;GACb,MAAM;GACN,QAAQ;GACR,OAAO;GACP;GACD,CAAC;;AAIN,MAAK,MAAM,CAAC,KAAK,UAAU,UAAU;AACnC,MAAI,UAAU,IAAI,IAAI,CAAE;AACxB,MAAI,KAAK;GAAE,UAAU,MAAM;GAAU,OAAO,MAAM;GAAO,MAAM;GAAS,OAAO;GAAO,CAAC;;AAGzF,QAAO,IAAI,MAAM,GAAG,MAAM;AACxB,MAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,WAAW,EAAE,WAAW,KAAK;AACrE,MAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AACzD,SAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI;GACpD;;AAGJ,SAAS,QAAQ,SAA6D;CAC5E,MAAM,sBAAM,IAAI,KAAyB;AACzC,MAAK,MAAM,SAAS,QAClB,KAAI,IAAI,GAAG,MAAM,SAAS,GAAG,MAAM,MAAM,GAAG,MAAM,UAAU,MAAM,MAAM;AAE1E,QAAO"}
1
+ {"version":3,"file":"diff.js","names":["COMPARED_FIELDS: ReadonlyArray<keyof ModelPrice>","out: PricingDiffEntry[]"],"sources":["../src/diff.ts"],"sourcesContent":["/**\n * `diffPricing(...)` - produce a row-by-row delta between two pricing\n * snapshots. Used by the upcoming `graphorin pricing diff` CLI to\n * surface upstream changes after a `refresh`.\n *\n * @packageDocumentation\n */\n\nimport type { ModelPrice, PricingDiffEntry, PricingSnapshot } from './types.js';\n\nconst COMPARED_FIELDS: ReadonlyArray<keyof ModelPrice> = [\n 'inputUsdPerToken',\n 'outputUsdPerToken',\n 'cachedReadUsdPerToken',\n 'cacheWriteUsdPerToken',\n 'reasoningUsdPerToken',\n 'region',\n 'notes',\n];\n\n/**\n * Compare two snapshots and return one entry per (provider, model)\n * pair that has been added, removed, or changed. The result is sorted\n * by `(provider, model, kind)` for deterministic output.\n *\n * @stable\n */\nexport function diffPricing(\n before: PricingSnapshot,\n after: PricingSnapshot,\n): ReadonlyArray<PricingDiffEntry> {\n const beforeMap = indexBy(before.entries);\n const afterMap = indexBy(after.entries);\n const out: PricingDiffEntry[] = [];\n\n for (const [key, entry] of beforeMap) {\n const next = afterMap.get(key);\n if (next === undefined) {\n out.push({ provider: entry.provider, model: entry.model, kind: 'removed', before: entry });\n continue;\n }\n const changedFields = COMPARED_FIELDS.filter((field) => entry[field] !== next[field]);\n if (changedFields.length > 0) {\n out.push({\n provider: entry.provider,\n model: entry.model,\n kind: 'changed',\n before: entry,\n after: next,\n changedFields,\n });\n }\n }\n\n for (const [key, entry] of afterMap) {\n if (beforeMap.has(key)) continue;\n out.push({ provider: entry.provider, model: entry.model, kind: 'added', after: entry });\n }\n\n return out.sort((a, b) => {\n if (a.provider !== b.provider) return a.provider < b.provider ? -1 : 1;\n if (a.model !== b.model) return a.model < b.model ? -1 : 1;\n return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;\n });\n}\n\nfunction indexBy(entries: ReadonlyArray<ModelPrice>): Map<string, ModelPrice> {\n const out = new Map<string, ModelPrice>();\n for (const entry of entries) {\n out.set(`${entry.provider}/${entry.model}/${entry.region ?? ''}`, entry);\n }\n return out;\n}\n"],"mappings":";AAUA,MAAMA,kBAAmD;CACvD;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;;;;;AASD,SAAgB,YACd,QACA,OACiC;CACjC,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,WAAW,QAAQ,MAAM,QAAQ;CACvC,MAAMC,MAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,KAAK,UAAU,WAAW;EACpC,MAAM,OAAO,SAAS,IAAI,IAAI;AAC9B,MAAI,SAAS,QAAW;AACtB,OAAI,KAAK;IAAE,UAAU,MAAM;IAAU,OAAO,MAAM;IAAO,MAAM;IAAW,QAAQ;IAAO,CAAC;AAC1F;;EAEF,MAAM,gBAAgB,gBAAgB,QAAQ,UAAU,MAAM,WAAW,KAAK,OAAO;AACrF,MAAI,cAAc,SAAS,EACzB,KAAI,KAAK;GACP,UAAU,MAAM;GAChB,OAAO,MAAM;GACb,MAAM;GACN,QAAQ;GACR,OAAO;GACP;GACD,CAAC;;AAIN,MAAK,MAAM,CAAC,KAAK,UAAU,UAAU;AACnC,MAAI,UAAU,IAAI,IAAI,CAAE;AACxB,MAAI,KAAK;GAAE,UAAU,MAAM;GAAU,OAAO,MAAM;GAAO,MAAM;GAAS,OAAO;GAAO,CAAC;;AAGzF,QAAO,IAAI,MAAM,GAAG,MAAM;AACxB,MAAI,EAAE,aAAa,EAAE,SAAU,QAAO,EAAE,WAAW,EAAE,WAAW,KAAK;AACrE,MAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AACzD,SAAO,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI;GACpD;;AAGJ,SAAS,QAAQ,SAA6D;CAC5E,MAAM,sBAAM,IAAI,KAAyB;AACzC,MAAK,MAAM,SAAS,QAClB,KAAI,IAAI,GAAG,MAAM,SAAS,GAAG,MAAM,MAAM,GAAG,MAAM,UAAU,MAAM,MAAM;AAE1E,QAAO"}
package/dist/index.d.ts CHANGED
@@ -9,33 +9,32 @@ import { BUNDLED_SNAPSHOT, SNAPSHOT_DATE, computeEntriesDigest } from "./snapsho
9
9
  //#region src/index.d.ts
10
10
 
11
11
  /**
12
- * @graphorin/pricing bundled LLM pricing snapshot for the Graphorin
12
+ * @graphorin/pricing - bundled LLM pricing snapshot for the Graphorin
13
13
  * framework.
14
14
  *
15
15
  * Highlights:
16
16
  *
17
- * - {@link BUNDLED_SNAPSHOT} bundled snapshot covering Anthropic,
17
+ * - {@link BUNDLED_SNAPSHOT} - bundled snapshot covering Anthropic,
18
18
  * OpenAI, Google, Mistral, Cohere + the local providers
19
19
  * (Ollama / llama.cpp). Each entry carries a per-token price and
20
20
  * the canonical SHA-256 digest of the `entries` array.
21
- * - {@link lookupPrice} resolve a `(provider, model)` pair against a
21
+ * - {@link lookupPrice} - resolve a `(provider, model)` pair against a
22
22
  * snapshot. Returns `null` for unknown entries plus emits one WARN
23
23
  * per process-lifetime per unknown pair.
24
- * - {@link calculateCost} multiply a price by token counts for a
24
+ * - {@link calculateCost} - multiply a price by token counts for a
25
25
  * single LLM call.
26
- * - {@link diffPricing} row-by-row delta between two snapshots.
27
- * - {@link refreshPricing} opt-in network call. Never automatic.
28
- * - {@link listMissingModels} scan trace spans for unknown models;
26
+ * - {@link diffPricing} - row-by-row delta between two snapshots.
27
+ * - {@link refreshPricing} - opt-in network call. Never automatic.
28
+ * - {@link listMissingModels} - scan trace spans for unknown models;
29
29
  * used by `graphorin pricing missing` (Phase 15).
30
30
  *
31
- * The bundled snapshot is intentionally small operators wanting the
31
+ * The bundled snapshot is intentionally small - operators wanting the
32
32
  * full upstream catalogue should run `refreshPricing(...)` and
33
33
  * persist the result to disk.
34
34
  *
35
35
  * @packageDocumentation
36
36
  */
37
- /** Canonical version constant. Mirrors the `package.json` version. */
38
- declare const VERSION = "0.5.0";
37
+ declare const VERSION: string;
39
38
  //#endregion
40
39
  export { BUNDLED_SNAPSHOT, type Cost, DEFAULT_PRICING_AUTO_REFRESH, type LookupPriceArgs, type LookupPriceResult, type MissingModelEntry, type ModelPrice, type PricingAutoRefreshConfig, type PricingConfig, type PricingDiffEntry, type PricingSnapshot, type PricingTraceSpanLike, type RefreshPricingOptions, SNAPSHOT_DATE, VERSION, _resetLookupWarningsForTesting, calculateCost, computeEntriesDigest, diffPricing, listMissingModels, lookupPrice, refreshPricing, setLookupWarnSink };
41
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA;;;;;;;;;;;;;;;;;;cAAa,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA;;;;;;;;;;;;;;;;;cAAa"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { version } from "./package.js";
1
2
  import { DEFAULT_PRICING_AUTO_REFRESH } from "./config.js";
2
3
  import { diffPricing } from "./diff.js";
3
4
  import { BUNDLED_SNAPSHOT, SNAPSHOT_DATE, computeEntriesDigest } from "./snapshot/bundled.js";
@@ -8,33 +9,33 @@ import "./snapshot/index.js";
8
9
 
9
10
  //#region src/index.ts
10
11
  /**
11
- * @graphorin/pricing bundled LLM pricing snapshot for the Graphorin
12
+ * @graphorin/pricing - bundled LLM pricing snapshot for the Graphorin
12
13
  * framework.
13
14
  *
14
15
  * Highlights:
15
16
  *
16
- * - {@link BUNDLED_SNAPSHOT} bundled snapshot covering Anthropic,
17
+ * - {@link BUNDLED_SNAPSHOT} - bundled snapshot covering Anthropic,
17
18
  * OpenAI, Google, Mistral, Cohere + the local providers
18
19
  * (Ollama / llama.cpp). Each entry carries a per-token price and
19
20
  * the canonical SHA-256 digest of the `entries` array.
20
- * - {@link lookupPrice} resolve a `(provider, model)` pair against a
21
+ * - {@link lookupPrice} - resolve a `(provider, model)` pair against a
21
22
  * snapshot. Returns `null` for unknown entries plus emits one WARN
22
23
  * per process-lifetime per unknown pair.
23
- * - {@link calculateCost} multiply a price by token counts for a
24
+ * - {@link calculateCost} - multiply a price by token counts for a
24
25
  * single LLM call.
25
- * - {@link diffPricing} row-by-row delta between two snapshots.
26
- * - {@link refreshPricing} opt-in network call. Never automatic.
27
- * - {@link listMissingModels} scan trace spans for unknown models;
26
+ * - {@link diffPricing} - row-by-row delta between two snapshots.
27
+ * - {@link refreshPricing} - opt-in network call. Never automatic.
28
+ * - {@link listMissingModels} - scan trace spans for unknown models;
28
29
  * used by `graphorin pricing missing` (Phase 15).
29
30
  *
30
- * The bundled snapshot is intentionally small operators wanting the
31
+ * The bundled snapshot is intentionally small - operators wanting the
31
32
  * full upstream catalogue should run `refreshPricing(...)` and
32
33
  * persist the result to disk.
33
34
  *
34
35
  * @packageDocumentation
35
36
  */
36
- /** Canonical version constant. Mirrors the `package.json` version. */
37
- const VERSION = "0.5.0";
37
+ /** Canonical version constant, derived from `package.json` at build time. */
38
+ const VERSION = version;
38
39
 
39
40
  //#endregion
40
41
  export { BUNDLED_SNAPSHOT, DEFAULT_PRICING_AUTO_REFRESH, SNAPSHOT_DATE, VERSION, _resetLookupWarningsForTesting, calculateCost, computeEntriesDigest, diffPricing, listMissingModels, lookupPrice, refreshPricing, setLookupWarnSink };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/pricing bundled LLM pricing snapshot for the Graphorin\n * framework.\n *\n * Highlights:\n *\n * - {@link BUNDLED_SNAPSHOT} bundled snapshot covering Anthropic,\n * OpenAI, Google, Mistral, Cohere + the local providers\n * (Ollama / llama.cpp). Each entry carries a per-token price and\n * the canonical SHA-256 digest of the `entries` array.\n * - {@link lookupPrice} resolve a `(provider, model)` pair against a\n * snapshot. Returns `null` for unknown entries plus emits one WARN\n * per process-lifetime per unknown pair.\n * - {@link calculateCost} multiply a price by token counts for a\n * single LLM call.\n * - {@link diffPricing} row-by-row delta between two snapshots.\n * - {@link refreshPricing} opt-in network call. Never automatic.\n * - {@link listMissingModels} scan trace spans for unknown models;\n * used by `graphorin pricing missing` (Phase 15).\n *\n * The bundled snapshot is intentionally small operators wanting the\n * full upstream catalogue should run `refreshPricing(...)` and\n * persist the result to disk.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.5.0';\n\nexport {\n DEFAULT_PRICING_AUTO_REFRESH,\n type PricingAutoRefreshConfig,\n type PricingConfig,\n} from './config.js';\nexport { diffPricing } from './diff.js';\nexport {\n _resetLookupWarningsForTesting,\n calculateCost,\n lookupPrice,\n setLookupWarnSink,\n} from './lookup.js';\nexport { listMissingModels, type MissingModelEntry } from './missing.js';\nexport { type RefreshPricingOptions, refreshPricing } from './refresh.js';\nexport { BUNDLED_SNAPSHOT, computeEntriesDigest, SNAPSHOT_DATE } from './snapshot/index.js';\nexport type {\n Cost,\n LookupPriceArgs,\n LookupPriceResult,\n ModelPrice,\n PricingDiffEntry,\n PricingSnapshot,\n PricingTraceSpanLike,\n} from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,MAAa,UAAU"}
1
+ {"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * @graphorin/pricing - bundled LLM pricing snapshot for the Graphorin\n * framework.\n *\n * Highlights:\n *\n * - {@link BUNDLED_SNAPSHOT} - bundled snapshot covering Anthropic,\n * OpenAI, Google, Mistral, Cohere + the local providers\n * (Ollama / llama.cpp). Each entry carries a per-token price and\n * the canonical SHA-256 digest of the `entries` array.\n * - {@link lookupPrice} - resolve a `(provider, model)` pair against a\n * snapshot. Returns `null` for unknown entries plus emits one WARN\n * per process-lifetime per unknown pair.\n * - {@link calculateCost} - multiply a price by token counts for a\n * single LLM call.\n * - {@link diffPricing} - row-by-row delta between two snapshots.\n * - {@link refreshPricing} - opt-in network call. Never automatic.\n * - {@link listMissingModels} - scan trace spans for unknown models;\n * used by `graphorin pricing missing` (Phase 15).\n *\n * The bundled snapshot is intentionally small - operators wanting the\n * full upstream catalogue should run `refreshPricing(...)` and\n * persist the result to disk.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport {\n DEFAULT_PRICING_AUTO_REFRESH,\n type PricingAutoRefreshConfig,\n type PricingConfig,\n} from './config.js';\nexport { diffPricing } from './diff.js';\nexport {\n _resetLookupWarningsForTesting,\n calculateCost,\n lookupPrice,\n setLookupWarnSink,\n} from './lookup.js';\nexport { listMissingModels, type MissingModelEntry } from './missing.js';\nexport { type RefreshPricingOptions, refreshPricing } from './refresh.js';\nexport { BUNDLED_SNAPSHOT, computeEntriesDigest, SNAPSHOT_DATE } from './snapshot/index.js';\nexport type {\n Cost,\n LookupPriceArgs,\n LookupPriceResult,\n ModelPrice,\n PricingDiffEntry,\n PricingSnapshot,\n PricingTraceSpanLike,\n} from './types.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAaA,UAAkBC"}
package/dist/lookup.d.ts CHANGED
@@ -10,7 +10,7 @@ import { LookupPriceArgs, LookupPriceResult, PricingSnapshot } from "./types.js"
10
10
  */
11
11
  declare function setLookupWarnSink(sink: (line: string) => void): void;
12
12
  /**
13
- * @internal exposed for tests so the WARN-once cache can be reset.
13
+ * @internal - exposed for tests so the WARN-once cache can be reset.
14
14
  */
15
15
  declare function _resetLookupWarningsForTesting(): void;
16
16
  /**
@@ -29,19 +29,23 @@ declare function lookupPrice(args: LookupPriceArgs, snapshot?: PricingSnapshot):
29
29
  * for a single LLM call without instantiating the cost tracker.
30
30
  *
31
31
  * Token-count contract (PS-19):
32
- * - `inputTokens` **excludes** `cachedReadTokens` cached reads are billed
33
- * separately at the cheaper cached rate, so pass the non-cached prompt count
34
- * to avoid double-billing.
32
+ * - `inputTokens` **excludes** `cachedReadTokens` and `cacheWriteTokens` -
33
+ * the cache legs are billed separately at their own rates, so pass the
34
+ * non-cached prompt count to avoid double-billing.
35
35
  * - `reasoningTokens` are billed at `outputUsdPerToken` unless the model entry
36
36
  * declares an explicit `reasoningUsdPerToken`.
37
+ * - `cacheWriteTokens` are billed at `cacheWriteUsdPerToken` when the entry
38
+ * declares one, else at the full input rate (a cache write is at minimum a
39
+ * normal input token - the fallback never under-bills relative to no cache).
37
40
  *
38
41
  * @stable
39
42
  */
40
43
  declare function calculateCost(args: LookupPriceArgs & {
41
- /** Non-cached prompt tokens (excludes `cachedReadTokens`). */
44
+ /** Non-cached prompt tokens (excludes cache reads AND cache writes). */
42
45
  readonly inputTokens: number;
43
46
  readonly outputTokens: number;
44
47
  readonly cachedReadTokens?: number;
48
+ readonly cacheWriteTokens?: number;
45
49
  readonly reasoningTokens?: number;
46
50
  }, snapshot?: PricingSnapshot): {
47
51
  readonly amount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"lookup.d.ts","names":[],"sources":["../src/lookup.ts"],"sourcesContent":[],"mappings":";;;;;AAyGA;;;;;iBArFgB,iBAAA;;;;iBASA,8BAAA,CAAA;;;;;;;;;;iBAaA,WAAA,OACR,4BACI,kBACT;;;;;;;;;;;;;;;iBA4Da,aAAA,OACR;;;;;;cAOI"}
1
+ {"version":3,"file":"lookup.d.ts","names":[],"sources":["../src/lookup.ts"],"sourcesContent":[],"mappings":";;;;;AA8HA;;;;;iBA1GgB,iBAAA;;;;iBASA,8BAAA,CAAA;;;;;;;;;;iBAaA,WAAA,OACR,4BACI,kBACT;;;;;;;;;;;;;;;;;;iBAiFa,aAAA,OACR;;;;;;;cAQI"}
package/dist/lookup.js CHANGED
@@ -2,7 +2,7 @@ import { BUNDLED_SNAPSHOT } from "./snapshot/bundled.js";
2
2
 
3
3
  //#region src/lookup.ts
4
4
  /**
5
- * `lookupPrice(...)` resolve a per-token price for a given
5
+ * `lookupPrice(...)` - resolve a per-token price for a given
6
6
  * (provider, model) pair against a snapshot. Returns `null` when the
7
7
  * model is unknown and emits one WARN per process-lifetime per
8
8
  * unknown (provider, model) pair.
@@ -21,7 +21,7 @@ function setLookupWarnSink(sink) {
21
21
  }
22
22
  let WARN_SINK = (line) => console.warn(line);
23
23
  /**
24
- * @internal exposed for tests so the WARN-once cache can be reset.
24
+ * @internal - exposed for tests so the WARN-once cache can be reset.
25
25
  */
26
26
  function _resetLookupWarningsForTesting() {
27
27
  WARNED.clear();
@@ -38,6 +38,11 @@ function _resetLookupWarningsForTesting() {
38
38
  function lookupPrice(args, snapshot = BUNDLED_SNAPSHOT) {
39
39
  const exact = snapshot.entries.find((entry) => entry.provider === args.provider && entry.model === args.model && (args.region === void 0 || entry.region === void 0 || entry.region === args.region));
40
40
  if (exact !== void 0) return entryToResult(exact, snapshot);
41
+ const dateless = args.model.replace(/-\d{8}$/, "");
42
+ if (dateless !== args.model) {
43
+ const alias = snapshot.entries.find((entry) => entry.provider === args.provider && entry.model === dateless && (args.region === void 0 || entry.region === void 0 || entry.region === args.region));
44
+ if (alias !== void 0) return entryToResult(alias, snapshot);
45
+ }
41
46
  const wildcard = snapshot.entries.find((entry) => entry.provider === args.provider && entry.model === "*");
42
47
  if (wildcard !== void 0) return entryToResult(wildcard, snapshot);
43
48
  warnOnce(args);
@@ -48,6 +53,7 @@ function entryToResult(entry, snapshot) {
48
53
  inputUsdPerToken: entry.inputUsdPerToken,
49
54
  outputUsdPerToken: entry.outputUsdPerToken,
50
55
  ...entry.cachedReadUsdPerToken === void 0 ? {} : { cachedReadUsdPerToken: entry.cachedReadUsdPerToken },
56
+ ...entry.cacheWriteUsdPerToken === void 0 ? {} : { cacheWriteUsdPerToken: entry.cacheWriteUsdPerToken },
51
57
  ...entry.reasoningUsdPerToken === void 0 ? {} : { reasoningUsdPerToken: entry.reasoningUsdPerToken },
52
58
  source: snapshot.source,
53
59
  snapshotDate: snapshot.snapshotDate
@@ -65,11 +71,14 @@ function warnOnce(args) {
65
71
  * for a single LLM call without instantiating the cost tracker.
66
72
  *
67
73
  * Token-count contract (PS-19):
68
- * - `inputTokens` **excludes** `cachedReadTokens` cached reads are billed
69
- * separately at the cheaper cached rate, so pass the non-cached prompt count
70
- * to avoid double-billing.
74
+ * - `inputTokens` **excludes** `cachedReadTokens` and `cacheWriteTokens` -
75
+ * the cache legs are billed separately at their own rates, so pass the
76
+ * non-cached prompt count to avoid double-billing.
71
77
  * - `reasoningTokens` are billed at `outputUsdPerToken` unless the model entry
72
78
  * declares an explicit `reasoningUsdPerToken`.
79
+ * - `cacheWriteTokens` are billed at `cacheWriteUsdPerToken` when the entry
80
+ * declares one, else at the full input rate (a cache write is at minimum a
81
+ * normal input token - the fallback never under-bills relative to no cache).
73
82
  *
74
83
  * @stable
75
84
  */
@@ -80,6 +89,7 @@ function calculateCost(args, snapshot = BUNDLED_SNAPSHOT) {
80
89
  amount += price.inputUsdPerToken * args.inputTokens;
81
90
  amount += price.outputUsdPerToken * args.outputTokens;
82
91
  if (args.cachedReadTokens !== void 0 && price.cachedReadUsdPerToken !== void 0) amount += price.cachedReadUsdPerToken * args.cachedReadTokens;
92
+ if (args.cacheWriteTokens !== void 0) amount += (price.cacheWriteUsdPerToken ?? price.inputUsdPerToken) * args.cacheWriteTokens;
83
93
  if (args.reasoningTokens !== void 0) amount += (price.reasoningUsdPerToken ?? price.outputUsdPerToken) * args.reasoningTokens;
84
94
  return {
85
95
  amount,
@@ -1 +1 @@
1
- {"version":3,"file":"lookup.js","names":["WARN_SINK: (line: string) => void"],"sources":["../src/lookup.ts"],"sourcesContent":["/**\n * `lookupPrice(...)` resolve a per-token price for a given\n * (provider, model) pair against a snapshot. Returns `null` when the\n * model is unknown and emits one WARN per process-lifetime per\n * unknown (provider, model) pair.\n *\n * @packageDocumentation\n */\n\nimport { BUNDLED_SNAPSHOT } from './snapshot/bundled.js';\nimport type { LookupPriceArgs, LookupPriceResult, ModelPrice, PricingSnapshot } from './types.js';\n\nconst WARNED = new Set<string>();\n\n/**\n * Optional sink for the deduplicated WARN emitted on unknown models.\n * Defaults to `console.warn`. Override in tests.\n *\n * @internal\n */\nexport function setLookupWarnSink(sink: (line: string) => void): void {\n WARN_SINK = sink;\n}\n\nlet WARN_SINK: (line: string) => void = (line) => console.warn(line);\n\n/**\n * @internal exposed for tests so the WARN-once cache can be reset.\n */\nexport function _resetLookupWarningsForTesting(): void {\n WARNED.clear();\n}\n\n/**\n * Resolve a per-token price for the (provider, model) pair. Returns\n * `null` when the snapshot does not contain an entry for the model.\n *\n * The function emits one WARN per process per unknown (provider, model)\n * pair so cost dashboards surface drift without spamming the log.\n *\n * @stable\n */\nexport function lookupPrice(\n args: LookupPriceArgs,\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): LookupPriceResult | null {\n const exact = snapshot.entries.find(\n (entry) =>\n entry.provider === args.provider &&\n entry.model === args.model &&\n (args.region === undefined || entry.region === undefined || entry.region === args.region),\n );\n\n if (exact !== undefined) return entryToResult(exact, snapshot);\n\n // Wildcard fallback used by local providers (`provider: 'ollama', model: '*'`).\n const wildcard = snapshot.entries.find(\n (entry) => entry.provider === args.provider && entry.model === '*',\n );\n if (wildcard !== undefined) return entryToResult(wildcard, snapshot);\n\n warnOnce(args);\n return null;\n}\n\nfunction entryToResult(entry: ModelPrice, snapshot: PricingSnapshot): LookupPriceResult {\n return {\n inputUsdPerToken: entry.inputUsdPerToken,\n outputUsdPerToken: entry.outputUsdPerToken,\n ...(entry.cachedReadUsdPerToken === undefined\n ? {}\n : { cachedReadUsdPerToken: entry.cachedReadUsdPerToken }),\n ...(entry.reasoningUsdPerToken === undefined\n ? {}\n : { reasoningUsdPerToken: entry.reasoningUsdPerToken }),\n source: snapshot.source,\n snapshotDate: snapshot.snapshotDate,\n };\n}\n\nfunction warnOnce(args: LookupPriceArgs): void {\n const key = `${args.provider}/${args.model}`;\n if (WARNED.has(key)) return;\n WARNED.add(key);\n WARN_SINK(\n `[graphorin/pricing] WARN: no pricing entry for ${args.provider}/${args.model}; ` +\n 'cost is null. Consider running `graphorin pricing refresh` or contributing the ' +\n 'entry upstream.',\n );\n}\n\n/**\n * Multiply a per-token price by an integer token count. Returns `null`\n * when the price is unknown. Useful when caller wants to compute cost\n * for a single LLM call without instantiating the cost tracker.\n *\n * Token-count contract (PS-19):\n * - `inputTokens` **excludes** `cachedReadTokens` cached reads are billed\n * separately at the cheaper cached rate, so pass the non-cached prompt count\n * to avoid double-billing.\n * - `reasoningTokens` are billed at `outputUsdPerToken` unless the model entry\n * declares an explicit `reasoningUsdPerToken`.\n *\n * @stable\n */\nexport function calculateCost(\n args: LookupPriceArgs & {\n /** Non-cached prompt tokens (excludes `cachedReadTokens`). */\n readonly inputTokens: number;\n readonly outputTokens: number;\n readonly cachedReadTokens?: number;\n readonly reasoningTokens?: number;\n },\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): { readonly amount: number; readonly currency: 'USD' } | null {\n const price = lookupPrice(args, snapshot);\n if (price === null) return null;\n let amount = 0;\n // `inputTokens` is the NON-cached prompt; cached reads are billed separately\n // at their own (cheaper) rate, so they must be excluded from `inputTokens` to\n // avoid double-counting (PS-19).\n amount += price.inputUsdPerToken * args.inputTokens;\n amount += price.outputUsdPerToken * args.outputTokens;\n if (args.cachedReadTokens !== undefined && price.cachedReadUsdPerToken !== undefined) {\n amount += price.cachedReadUsdPerToken * args.cachedReadTokens;\n }\n if (args.reasoningTokens !== undefined) {\n // PS-19: reasoning tokens follow completion (output) pricing unless the\n // entry declares an explicit `reasoningUsdPerToken` the documented\n // contract that was previously billed at $0 for every bundled entry.\n amount += (price.reasoningUsdPerToken ?? price.outputUsdPerToken) * args.reasoningTokens;\n }\n return { amount, currency: 'USD' };\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,yBAAS,IAAI,KAAa;;;;;;;AAQhC,SAAgB,kBAAkB,MAAoC;AACpE,aAAY;;AAGd,IAAIA,aAAqC,SAAS,QAAQ,KAAK,KAAK;;;;AAKpE,SAAgB,iCAAuC;AACrD,QAAO,OAAO;;;;;;;;;;;AAYhB,SAAgB,YACd,MACA,WAA4B,kBACF;CAC1B,MAAM,QAAQ,SAAS,QAAQ,MAC5B,UACC,MAAM,aAAa,KAAK,YACxB,MAAM,UAAU,KAAK,UACpB,KAAK,WAAW,UAAa,MAAM,WAAW,UAAa,MAAM,WAAW,KAAK,QACrF;AAED,KAAI,UAAU,OAAW,QAAO,cAAc,OAAO,SAAS;CAG9D,MAAM,WAAW,SAAS,QAAQ,MAC/B,UAAU,MAAM,aAAa,KAAK,YAAY,MAAM,UAAU,IAChE;AACD,KAAI,aAAa,OAAW,QAAO,cAAc,UAAU,SAAS;AAEpE,UAAS,KAAK;AACd,QAAO;;AAGT,SAAS,cAAc,OAAmB,UAA8C;AACtF,QAAO;EACL,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB,GAAI,MAAM,0BAA0B,SAChC,EAAE,GACF,EAAE,uBAAuB,MAAM,uBAAuB;EAC1D,GAAI,MAAM,yBAAyB,SAC/B,EAAE,GACF,EAAE,sBAAsB,MAAM,sBAAsB;EACxD,QAAQ,SAAS;EACjB,cAAc,SAAS;EACxB;;AAGH,SAAS,SAAS,MAA6B;CAC7C,MAAM,MAAM,GAAG,KAAK,SAAS,GAAG,KAAK;AACrC,KAAI,OAAO,IAAI,IAAI,CAAE;AACrB,QAAO,IAAI,IAAI;AACf,WACE,kDAAkD,KAAK,SAAS,GAAG,KAAK,MAAM,oGAG/E;;;;;;;;;;;;;;;;AAiBH,SAAgB,cACd,MAOA,WAA4B,kBACkC;CAC9D,MAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,KAAI,UAAU,KAAM,QAAO;CAC3B,IAAI,SAAS;AAIb,WAAU,MAAM,mBAAmB,KAAK;AACxC,WAAU,MAAM,oBAAoB,KAAK;AACzC,KAAI,KAAK,qBAAqB,UAAa,MAAM,0BAA0B,OACzE,WAAU,MAAM,wBAAwB,KAAK;AAE/C,KAAI,KAAK,oBAAoB,OAI3B,YAAW,MAAM,wBAAwB,MAAM,qBAAqB,KAAK;AAE3E,QAAO;EAAE;EAAQ,UAAU;EAAO"}
1
+ {"version":3,"file":"lookup.js","names":["WARN_SINK: (line: string) => void"],"sources":["../src/lookup.ts"],"sourcesContent":["/**\n * `lookupPrice(...)` - resolve a per-token price for a given\n * (provider, model) pair against a snapshot. Returns `null` when the\n * model is unknown and emits one WARN per process-lifetime per\n * unknown (provider, model) pair.\n *\n * @packageDocumentation\n */\n\nimport { BUNDLED_SNAPSHOT } from './snapshot/bundled.js';\nimport type { LookupPriceArgs, LookupPriceResult, ModelPrice, PricingSnapshot } from './types.js';\n\nconst WARNED = new Set<string>();\n\n/**\n * Optional sink for the deduplicated WARN emitted on unknown models.\n * Defaults to `console.warn`. Override in tests.\n *\n * @internal\n */\nexport function setLookupWarnSink(sink: (line: string) => void): void {\n WARN_SINK = sink;\n}\n\nlet WARN_SINK: (line: string) => void = (line) => console.warn(line);\n\n/**\n * @internal - exposed for tests so the WARN-once cache can be reset.\n */\nexport function _resetLookupWarningsForTesting(): void {\n WARNED.clear();\n}\n\n/**\n * Resolve a per-token price for the (provider, model) pair. Returns\n * `null` when the snapshot does not contain an entry for the model.\n *\n * The function emits one WARN per process per unknown (provider, model)\n * pair so cost dashboards surface drift without spamming the log.\n *\n * @stable\n */\nexport function lookupPrice(\n args: LookupPriceArgs,\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): LookupPriceResult | null {\n const exact = snapshot.entries.find(\n (entry) =>\n entry.provider === args.provider &&\n entry.model === args.model &&\n (args.region === undefined || entry.region === undefined || entry.region === args.region),\n );\n\n if (exact !== undefined) return entryToResult(exact, snapshot);\n\n // Date-suffix fallback (core-provider-03): dated ids like\n // `claude-haiku-4-5-20251001` resolve to their dateless alias entry\n // (`claude-haiku-4-5`) so the snapshot does not need one row per\n // dated release.\n const dateless = args.model.replace(/-\\d{8}$/, '');\n if (dateless !== args.model) {\n const alias = snapshot.entries.find(\n (entry) =>\n entry.provider === args.provider &&\n entry.model === dateless &&\n (args.region === undefined || entry.region === undefined || entry.region === args.region),\n );\n if (alias !== undefined) return entryToResult(alias, snapshot);\n }\n\n // Wildcard fallback - used by local providers (`provider: 'ollama', model: '*'`).\n const wildcard = snapshot.entries.find(\n (entry) => entry.provider === args.provider && entry.model === '*',\n );\n if (wildcard !== undefined) return entryToResult(wildcard, snapshot);\n\n warnOnce(args);\n return null;\n}\n\nfunction entryToResult(entry: ModelPrice, snapshot: PricingSnapshot): LookupPriceResult {\n return {\n inputUsdPerToken: entry.inputUsdPerToken,\n outputUsdPerToken: entry.outputUsdPerToken,\n ...(entry.cachedReadUsdPerToken === undefined\n ? {}\n : { cachedReadUsdPerToken: entry.cachedReadUsdPerToken }),\n ...(entry.cacheWriteUsdPerToken === undefined\n ? {}\n : { cacheWriteUsdPerToken: entry.cacheWriteUsdPerToken }),\n ...(entry.reasoningUsdPerToken === undefined\n ? {}\n : { reasoningUsdPerToken: entry.reasoningUsdPerToken }),\n source: snapshot.source,\n snapshotDate: snapshot.snapshotDate,\n };\n}\n\nfunction warnOnce(args: LookupPriceArgs): void {\n const key = `${args.provider}/${args.model}`;\n if (WARNED.has(key)) return;\n WARNED.add(key);\n WARN_SINK(\n `[graphorin/pricing] WARN: no pricing entry for ${args.provider}/${args.model}; ` +\n 'cost is null. Consider running `graphorin pricing refresh` or contributing the ' +\n 'entry upstream.',\n );\n}\n\n/**\n * Multiply a per-token price by an integer token count. Returns `null`\n * when the price is unknown. Useful when caller wants to compute cost\n * for a single LLM call without instantiating the cost tracker.\n *\n * Token-count contract (PS-19):\n * - `inputTokens` **excludes** `cachedReadTokens` and `cacheWriteTokens` -\n * the cache legs are billed separately at their own rates, so pass the\n * non-cached prompt count to avoid double-billing.\n * - `reasoningTokens` are billed at `outputUsdPerToken` unless the model entry\n * declares an explicit `reasoningUsdPerToken`.\n * - `cacheWriteTokens` are billed at `cacheWriteUsdPerToken` when the entry\n * declares one, else at the full input rate (a cache write is at minimum a\n * normal input token - the fallback never under-bills relative to no cache).\n *\n * @stable\n */\nexport function calculateCost(\n args: LookupPriceArgs & {\n /** Non-cached prompt tokens (excludes cache reads AND cache writes). */\n readonly inputTokens: number;\n readonly outputTokens: number;\n readonly cachedReadTokens?: number;\n readonly cacheWriteTokens?: number;\n readonly reasoningTokens?: number;\n },\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): { readonly amount: number; readonly currency: 'USD' } | null {\n const price = lookupPrice(args, snapshot);\n if (price === null) return null;\n let amount = 0;\n // `inputTokens` is the NON-cached prompt; cached reads are billed separately\n // at their own (cheaper) rate, so they must be excluded from `inputTokens` to\n // avoid double-counting (PS-19).\n amount += price.inputUsdPerToken * args.inputTokens;\n amount += price.outputUsdPerToken * args.outputTokens;\n if (args.cachedReadTokens !== undefined && price.cachedReadUsdPerToken !== undefined) {\n amount += price.cachedReadUsdPerToken * args.cachedReadTokens;\n }\n if (args.cacheWriteTokens !== undefined) {\n amount += (price.cacheWriteUsdPerToken ?? price.inputUsdPerToken) * args.cacheWriteTokens;\n }\n if (args.reasoningTokens !== undefined) {\n // PS-19: reasoning tokens follow completion (output) pricing unless the\n // entry declares an explicit `reasoningUsdPerToken` - the documented\n // contract that was previously billed at $0 for every bundled entry.\n amount += (price.reasoningUsdPerToken ?? price.outputUsdPerToken) * args.reasoningTokens;\n }\n return { amount, currency: 'USD' };\n}\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,yBAAS,IAAI,KAAa;;;;;;;AAQhC,SAAgB,kBAAkB,MAAoC;AACpE,aAAY;;AAGd,IAAIA,aAAqC,SAAS,QAAQ,KAAK,KAAK;;;;AAKpE,SAAgB,iCAAuC;AACrD,QAAO,OAAO;;;;;;;;;;;AAYhB,SAAgB,YACd,MACA,WAA4B,kBACF;CAC1B,MAAM,QAAQ,SAAS,QAAQ,MAC5B,UACC,MAAM,aAAa,KAAK,YACxB,MAAM,UAAU,KAAK,UACpB,KAAK,WAAW,UAAa,MAAM,WAAW,UAAa,MAAM,WAAW,KAAK,QACrF;AAED,KAAI,UAAU,OAAW,QAAO,cAAc,OAAO,SAAS;CAM9D,MAAM,WAAW,KAAK,MAAM,QAAQ,WAAW,GAAG;AAClD,KAAI,aAAa,KAAK,OAAO;EAC3B,MAAM,QAAQ,SAAS,QAAQ,MAC5B,UACC,MAAM,aAAa,KAAK,YACxB,MAAM,UAAU,aACf,KAAK,WAAW,UAAa,MAAM,WAAW,UAAa,MAAM,WAAW,KAAK,QACrF;AACD,MAAI,UAAU,OAAW,QAAO,cAAc,OAAO,SAAS;;CAIhE,MAAM,WAAW,SAAS,QAAQ,MAC/B,UAAU,MAAM,aAAa,KAAK,YAAY,MAAM,UAAU,IAChE;AACD,KAAI,aAAa,OAAW,QAAO,cAAc,UAAU,SAAS;AAEpE,UAAS,KAAK;AACd,QAAO;;AAGT,SAAS,cAAc,OAAmB,UAA8C;AACtF,QAAO;EACL,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB,GAAI,MAAM,0BAA0B,SAChC,EAAE,GACF,EAAE,uBAAuB,MAAM,uBAAuB;EAC1D,GAAI,MAAM,0BAA0B,SAChC,EAAE,GACF,EAAE,uBAAuB,MAAM,uBAAuB;EAC1D,GAAI,MAAM,yBAAyB,SAC/B,EAAE,GACF,EAAE,sBAAsB,MAAM,sBAAsB;EACxD,QAAQ,SAAS;EACjB,cAAc,SAAS;EACxB;;AAGH,SAAS,SAAS,MAA6B;CAC7C,MAAM,MAAM,GAAG,KAAK,SAAS,GAAG,KAAK;AACrC,KAAI,OAAO,IAAI,IAAI,CAAE;AACrB,QAAO,IAAI,IAAI;AACf,WACE,kDAAkD,KAAK,SAAS,GAAG,KAAK,MAAM,oGAG/E;;;;;;;;;;;;;;;;;;;AAoBH,SAAgB,cACd,MAQA,WAA4B,kBACkC;CAC9D,MAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,KAAI,UAAU,KAAM,QAAO;CAC3B,IAAI,SAAS;AAIb,WAAU,MAAM,mBAAmB,KAAK;AACxC,WAAU,MAAM,oBAAoB,KAAK;AACzC,KAAI,KAAK,qBAAqB,UAAa,MAAM,0BAA0B,OACzE,WAAU,MAAM,wBAAwB,KAAK;AAE/C,KAAI,KAAK,qBAAqB,OAC5B,YAAW,MAAM,yBAAyB,MAAM,oBAAoB,KAAK;AAE3E,KAAI,KAAK,oBAAoB,OAI3B,YAAW,MAAM,wBAAwB,MAAM,qBAAqB,KAAK;AAE3E,QAAO;EAAE;EAAQ,UAAU;EAAO"}
package/dist/missing.js CHANGED
@@ -3,7 +3,7 @@ import { lookupPrice } from "./lookup.js";
3
3
 
4
4
  //#region src/missing.ts
5
5
  /**
6
- * `listMissingModels(...)` scan a sequence of trace spans and return
6
+ * `listMissingModels(...)` - scan a sequence of trace spans and return
7
7
  * the unique (provider, model) pairs that the snapshot does not
8
8
  * recognise.
9
9
  *
@@ -23,7 +23,11 @@ import { lookupPrice } from "./lookup.js";
23
23
  function listMissingModels(spans, snapshot = BUNDLED_SNAPSHOT) {
24
24
  const seen = /* @__PURE__ */ new Map();
25
25
  for (const span of spans) {
26
- const provider = stringAttr(span, ["gen_ai.system", "graphorin.provider.id"]);
26
+ const provider = stringAttr(span, [
27
+ "gen_ai.provider.name",
28
+ "gen_ai.system",
29
+ "graphorin.provider.id"
30
+ ]);
27
31
  const model = stringAttr(span, [
28
32
  "gen_ai.request.model",
29
33
  "gen_ai.response.model",
@@ -1 +1 @@
1
- {"version":3,"file":"missing.js","names":[],"sources":["../src/missing.ts"],"sourcesContent":["/**\n * `listMissingModels(...)` scan a sequence of trace spans and return\n * the unique (provider, model) pairs that the snapshot does not\n * recognise.\n *\n * The function reads `gen_ai.system` + `gen_ai.request.model` (the\n * canonical OpenTelemetry GenAI attributes) and falls back to the\n * Graphorin-prefixed `graphorin.provider.id` /\n * `graphorin.provider.model` pair when `gen_ai.*` is absent.\n *\n * @packageDocumentation\n */\n\nimport { lookupPrice } from './lookup.js';\nimport { BUNDLED_SNAPSHOT } from './snapshot/bundled.js';\nimport type { PricingSnapshot, PricingTraceSpanLike } from './types.js';\n\n/**\n * Single missing-model row.\n *\n * @stable\n */\nexport interface MissingModelEntry {\n readonly provider: string;\n readonly model: string;\n readonly count: number;\n}\n\n/**\n * Return one entry per (provider, model) pair that the snapshot does\n * not recognise, sorted by descending occurrence count.\n *\n * @stable\n */\nexport function listMissingModels(\n spans: Iterable<PricingTraceSpanLike>,\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): ReadonlyArray<MissingModelEntry> {\n const seen = new Map<string, MissingModelEntry & { count: number }>();\n for (const span of spans) {\n const provider = stringAttr(span, ['gen_ai.system', 'graphorin.provider.id']);\n const model = stringAttr(span, [\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'graphorin.provider.model',\n ]);\n if (provider === null || model === null) continue;\n if (lookupPrice({ provider, model }, snapshot) !== null) continue;\n const key = `${provider}/${model}`;\n const existing = seen.get(key);\n if (existing === undefined) {\n seen.set(key, { provider, model, count: 1 });\n } else {\n seen.set(key, { provider, model, count: existing.count + 1 });\n }\n }\n return [...seen.values()].sort((a, b) => b.count - a.count);\n}\n\nfunction stringAttr(span: PricingTraceSpanLike, keys: ReadonlyArray<string>): string | null {\n for (const key of keys) {\n const value = span.attributes[key];\n if (typeof value === 'string' && value.length > 0) return value;\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,kBACd,OACA,WAA4B,kBACM;CAClC,MAAM,uBAAO,IAAI,KAAoD;AACrE,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,WAAW,MAAM,CAAC,iBAAiB,wBAAwB,CAAC;EAC7E,MAAM,QAAQ,WAAW,MAAM;GAC7B;GACA;GACA;GACD,CAAC;AACF,MAAI,aAAa,QAAQ,UAAU,KAAM;AACzC,MAAI,YAAY;GAAE;GAAU;GAAO,EAAE,SAAS,KAAK,KAAM;EACzD,MAAM,MAAM,GAAG,SAAS,GAAG;EAC3B,MAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,MAAI,aAAa,OACf,MAAK,IAAI,KAAK;GAAE;GAAU;GAAO,OAAO;GAAG,CAAC;MAE5C,MAAK,IAAI,KAAK;GAAE;GAAU;GAAO,OAAO,SAAS,QAAQ;GAAG,CAAC;;AAGjE,QAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;;AAG7D,SAAS,WAAW,MAA4B,MAA4C;AAC1F,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,KAAK,WAAW;AAC9B,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;;AAE5D,QAAO"}
1
+ {"version":3,"file":"missing.js","names":[],"sources":["../src/missing.ts"],"sourcesContent":["/**\n * `listMissingModels(...)` - scan a sequence of trace spans and return\n * the unique (provider, model) pairs that the snapshot does not\n * recognise.\n *\n * The function reads `gen_ai.system` + `gen_ai.request.model` (the\n * canonical OpenTelemetry GenAI attributes) and falls back to the\n * Graphorin-prefixed `graphorin.provider.id` /\n * `graphorin.provider.model` pair when `gen_ai.*` is absent.\n *\n * @packageDocumentation\n */\n\nimport { lookupPrice } from './lookup.js';\nimport { BUNDLED_SNAPSHOT } from './snapshot/bundled.js';\nimport type { PricingSnapshot, PricingTraceSpanLike } from './types.js';\n\n/**\n * Single missing-model row.\n *\n * @stable\n */\nexport interface MissingModelEntry {\n readonly provider: string;\n readonly model: string;\n readonly count: number;\n}\n\n/**\n * Return one entry per (provider, model) pair that the snapshot does\n * not recognise, sorted by descending occurrence count.\n *\n * @stable\n */\nexport function listMissingModels(\n spans: Iterable<PricingTraceSpanLike>,\n snapshot: PricingSnapshot = BUNDLED_SNAPSHOT,\n): ReadonlyArray<MissingModelEntry> {\n const seen = new Map<string, MissingModelEntry & { count: number }>();\n for (const span of spans) {\n // E8: `gen_ai.provider.name` is the current OTel GenAI attribute (what\n // withTracing emits); `gen_ai.system` is its deprecated predecessor kept\n // for older recorded traces.\n const provider = stringAttr(span, [\n 'gen_ai.provider.name',\n 'gen_ai.system',\n 'graphorin.provider.id',\n ]);\n const model = stringAttr(span, [\n 'gen_ai.request.model',\n 'gen_ai.response.model',\n 'graphorin.provider.model',\n ]);\n if (provider === null || model === null) continue;\n if (lookupPrice({ provider, model }, snapshot) !== null) continue;\n const key = `${provider}/${model}`;\n const existing = seen.get(key);\n if (existing === undefined) {\n seen.set(key, { provider, model, count: 1 });\n } else {\n seen.set(key, { provider, model, count: existing.count + 1 });\n }\n }\n return [...seen.values()].sort((a, b) => b.count - a.count);\n}\n\nfunction stringAttr(span: PricingTraceSpanLike, keys: ReadonlyArray<string>): string | null {\n for (const key of keys) {\n const value = span.attributes[key];\n if (typeof value === 'string' && value.length > 0) return value;\n }\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,kBACd,OACA,WAA4B,kBACM;CAClC,MAAM,uBAAO,IAAI,KAAoD;AACrE,MAAK,MAAM,QAAQ,OAAO;EAIxB,MAAM,WAAW,WAAW,MAAM;GAChC;GACA;GACA;GACD,CAAC;EACF,MAAM,QAAQ,WAAW,MAAM;GAC7B;GACA;GACA;GACD,CAAC;AACF,MAAI,aAAa,QAAQ,UAAU,KAAM;AACzC,MAAI,YAAY;GAAE;GAAU;GAAO,EAAE,SAAS,KAAK,KAAM;EACzD,MAAM,MAAM,GAAG,SAAS,GAAG;EAC3B,MAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,MAAI,aAAa,OACf,MAAK,IAAI,KAAK;GAAE;GAAU;GAAO,OAAO;GAAG,CAAC;MAE5C,MAAK,IAAI,KAAK;GAAE;GAAU;GAAO,OAAO,SAAS,QAAQ;GAAG,CAAC;;AAGjE,QAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;;AAG7D,SAAS,WAAW,MAA4B,MAA4C;AAC1F,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,KAAK,WAAW;AAC9B,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;;AAE5D,QAAO"}
@@ -0,0 +1,6 @@
1
+ //#region package.json
2
+ var version = "0.6.1";
3
+
4
+ //#endregion
5
+ export { version };
6
+ //# sourceMappingURL=package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/pricing\",\n \"version\": \"0.6.1\",\n \"description\": \"Bundled LLM pricing snapshot for the Graphorin framework. Ships per-provider, per-model input / output / cached-read prices alongside provenance metadata, a `lookupPrice(...)` resolver, snapshot diffing utilities, an opt-in `refreshPricing(...)` hook (network gated; never automatic), and `listMissingModels(...)` for trace audits.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/pricing\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/pricing\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"pricing\",\n \"tokens\",\n \"cost\",\n \"llm\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./snapshot\": {\n \"types\": \"./dist/snapshot/index.d.ts\",\n \"import\": \"./dist/snapshot/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/core\": \"workspace:*\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {}\n}\n"],"mappings":";cAEa"}
package/dist/refresh.d.ts CHANGED
@@ -10,11 +10,11 @@ import { PricingSnapshot } from "./types.js";
10
10
  * @stable
11
11
  */
12
12
  interface RefreshPricingOptions {
13
- /** Snapshot URL typically the upstream pricing JSON. */
13
+ /** Snapshot URL - typically the upstream pricing JSON. */
14
14
  readonly url: string;
15
15
  /** Optional headers (auth, conditional GET, etc.). */
16
16
  readonly headers?: Readonly<Record<string, string>>;
17
- /** Optional fetch override useful in tests. */
17
+ /** Optional fetch override - useful in tests. */
18
18
  readonly fetchImpl?: typeof fetch;
19
19
  /** Override the snapshot date stamped on the result. Defaults to today. */
20
20
  readonly snapshotDate?: string;
package/dist/refresh.js CHANGED
@@ -2,7 +2,7 @@ import { computeEntriesDigest } from "./snapshot/bundled.js";
2
2
 
3
3
  //#region src/refresh.ts
4
4
  /**
5
- * `refreshPricing(...)` opt-in network call. The framework never
5
+ * `refreshPricing(...)` - opt-in network call. The framework never
6
6
  * invokes this automatically; it exists so the CLI (`graphorin
7
7
  * pricing refresh`) and operator scripts can pull a fresh snapshot
8
8
  * without re-installing the package.
@@ -72,6 +72,7 @@ function parseEntry(raw) {
72
72
  inputUsdPerToken: e.inputUsdPerToken,
73
73
  outputUsdPerToken: e.outputUsdPerToken,
74
74
  ...typeof e.cachedReadUsdPerToken === "number" ? { cachedReadUsdPerToken: e.cachedReadUsdPerToken } : {},
75
+ ...typeof e.cacheWriteUsdPerToken === "number" ? { cacheWriteUsdPerToken: e.cacheWriteUsdPerToken } : {},
75
76
  ...typeof e.reasoningUsdPerToken === "number" ? { reasoningUsdPerToken: e.reasoningUsdPerToken } : {},
76
77
  ...typeof e.region === "string" ? { region: e.region } : {},
77
78
  ...typeof e.notes === "string" ? { notes: e.notes } : {}
@@ -1 +1 @@
1
- {"version":3,"file":"refresh.js","names":["res: Awaited<ReturnType<typeof fetchImpl>>","raw: unknown"],"sources":["../src/refresh.ts"],"sourcesContent":["/**\n * `refreshPricing(...)` opt-in network call. The framework never\n * invokes this automatically; it exists so the CLI (`graphorin\n * pricing refresh`) and operator scripts can pull a fresh snapshot\n * without re-installing the package.\n *\n * The function fetches the supplied URL as JSON and validates the\n * shape against the {@link PricingSnapshot} contract. The bundled\n * snapshot is **not** mutated; callers persist the refreshed\n * snapshot to disk and reload it explicitly.\n *\n * @packageDocumentation\n */\n\nimport { computeEntriesDigest } from './snapshot/bundled.js';\nimport type { ModelPrice, PricingSnapshot } from './types.js';\n\n/**\n * Configuration shape for {@link refreshPricing}. The `fetchImpl`\n * override exists so tests can exercise the function without making\n * a real network call.\n *\n * @stable\n */\nexport interface RefreshPricingOptions {\n /** Snapshot URL typically the upstream pricing JSON. */\n readonly url: string;\n /** Optional headers (auth, conditional GET, etc.). */\n readonly headers?: Readonly<Record<string, string>>;\n /** Optional fetch override useful in tests. */\n readonly fetchImpl?: typeof fetch;\n /** Override the snapshot date stamped on the result. Defaults to today. */\n readonly snapshotDate?: string;\n /** Override the snapshot version string. Defaults to `'graphorin/0.1+refreshed'`. */\n readonly version?: string;\n /**\n * Hard timeout for the network fetch in milliseconds. Default\n * `30000`. Aborts the request (and throws) if the upstream is slow\n * or unreachable so `graphorin pricing refresh` cannot hang. Pass an\n * explicit {@link RefreshPricingOptions.signal} to manage\n * cancellation yourself; the two are combined.\n */\n readonly timeoutMs?: number;\n /** Caller-supplied abort signal, combined with the timeout. */\n readonly signal?: AbortSignal;\n}\n\n/**\n * Pull a fresh snapshot from the supplied URL and return it. Network\n * failures and shape mismatches surface as thrown errors so the CLI\n * can surface them to the operator.\n *\n * @stable\n */\nexport async function refreshPricing(opts: RefreshPricingOptions): Promise<PricingSnapshot> {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new TypeError(\n 'refreshPricing: no fetch implementation available. Provide `fetchImpl` ' +\n 'explicitly or run on a Node.js version with global fetch (>= 18).',\n );\n }\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal =\n opts.signal === undefined ? timeoutSignal : AbortSignal.any([opts.signal, timeoutSignal]);\n let res: Awaited<ReturnType<typeof fetchImpl>>;\n try {\n res = await fetchImpl(opts.url, { headers: opts.headers ?? {}, signal });\n } catch (err) {\n if (timeoutSignal.aborted) {\n throw new Error(`refreshPricing: timed out after ${timeoutMs} ms fetching ${opts.url}`, {\n cause: err,\n });\n }\n throw err;\n }\n if (!res.ok) {\n throw new Error(`refreshPricing: HTTP ${res.status} ${res.statusText} from ${opts.url}`);\n }\n const body = (await res.json()) as unknown;\n const entries = parseEntries(body);\n const snapshotDate = opts.snapshotDate ?? new Date().toISOString().slice(0, 10);\n const version = opts.version ?? 'graphorin/0.1+refreshed';\n return Object.freeze<PricingSnapshot>({\n version,\n source: opts.url,\n snapshotDate,\n currency: 'USD',\n sha256: computeEntriesDigest(entries),\n entries,\n });\n}\n\nfunction parseEntries(body: unknown): ReadonlyArray<ModelPrice> {\n if (body === null || typeof body !== 'object') {\n throw new Error('refreshPricing: response body is not an object');\n }\n // Two accepted shapes:\n // - `{ entries: ModelPrice[] }`\n // - `ModelPrice[]`\n let raw: unknown;\n if (Array.isArray(body)) {\n raw = body;\n } else {\n raw = (body as { entries?: unknown }).entries;\n if (!Array.isArray(raw)) {\n throw new Error('refreshPricing: response object is missing the `entries` array');\n }\n }\n const entries = (raw as ReadonlyArray<unknown>).map(parseEntry);\n return Object.freeze(entries);\n}\n\nfunction parseEntry(raw: unknown): ModelPrice {\n if (raw === null || typeof raw !== 'object') {\n throw new Error('refreshPricing: pricing entry is not an object');\n }\n const e = raw as Partial<ModelPrice>;\n if (typeof e.provider !== 'string' || typeof e.model !== 'string') {\n throw new Error('refreshPricing: pricing entry is missing provider / model');\n }\n if (typeof e.inputUsdPerToken !== 'number' || typeof e.outputUsdPerToken !== 'number') {\n throw new Error(\n `refreshPricing: pricing entry ${e.provider}/${e.model} is missing input / output prices`,\n );\n }\n return Object.freeze({\n provider: e.provider,\n model: e.model,\n inputUsdPerToken: e.inputUsdPerToken,\n outputUsdPerToken: e.outputUsdPerToken,\n ...(typeof e.cachedReadUsdPerToken === 'number'\n ? { cachedReadUsdPerToken: e.cachedReadUsdPerToken }\n : {}),\n ...(typeof e.reasoningUsdPerToken === 'number'\n ? { reasoningUsdPerToken: e.reasoningUsdPerToken }\n : {}),\n ...(typeof e.region === 'string' ? { region: e.region } : {}),\n ...(typeof e.notes === 'string' ? { notes: e.notes } : {}),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsDA,eAAsB,eAAe,MAAuD;CAC1F,MAAM,YAAY,KAAK,aAAa,WAAW;AAC/C,KAAI,OAAO,cAAc,WACvB,OAAM,IAAI,UACR,2IAED;CAEH,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,gBAAgB,YAAY,QAAQ,UAAU;CACpD,MAAM,SACJ,KAAK,WAAW,SAAY,gBAAgB,YAAY,IAAI,CAAC,KAAK,QAAQ,cAAc,CAAC;CAC3F,IAAIA;AACJ,KAAI;AACF,QAAM,MAAM,UAAU,KAAK,KAAK;GAAE,SAAS,KAAK,WAAW,EAAE;GAAE;GAAQ,CAAC;UACjE,KAAK;AACZ,MAAI,cAAc,QAChB,OAAM,IAAI,MAAM,mCAAmC,UAAU,eAAe,KAAK,OAAO,EACtF,OAAO,KACR,CAAC;AAEJ,QAAM;;AAER,KAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,IAAI,WAAW,QAAQ,KAAK,MAAM;CAG1F,MAAM,UAAU,aADF,MAAM,IAAI,MAAM,CACI;CAClC,MAAM,eAAe,KAAK,iCAAgB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;CAC/E,MAAM,UAAU,KAAK,WAAW;AAChC,QAAO,OAAO,OAAwB;EACpC;EACA,QAAQ,KAAK;EACb;EACA,UAAU;EACV,QAAQ,qBAAqB,QAAQ;EACrC;EACD,CAAC;;AAGJ,SAAS,aAAa,MAA0C;AAC9D,KAAI,SAAS,QAAQ,OAAO,SAAS,SACnC,OAAM,IAAI,MAAM,iDAAiD;CAKnE,IAAIC;AACJ,KAAI,MAAM,QAAQ,KAAK,CACrB,OAAM;MACD;AACL,QAAO,KAA+B;AACtC,MAAI,CAAC,MAAM,QAAQ,IAAI,CACrB,OAAM,IAAI,MAAM,iEAAiE;;CAGrF,MAAM,UAAW,IAA+B,IAAI,WAAW;AAC/D,QAAO,OAAO,OAAO,QAAQ;;AAG/B,SAAS,WAAW,KAA0B;AAC5C,KAAI,QAAQ,QAAQ,OAAO,QAAQ,SACjC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,IAAI;AACV,KAAI,OAAO,EAAE,aAAa,YAAY,OAAO,EAAE,UAAU,SACvD,OAAM,IAAI,MAAM,4DAA4D;AAE9E,KAAI,OAAO,EAAE,qBAAqB,YAAY,OAAO,EAAE,sBAAsB,SAC3E,OAAM,IAAI,MACR,iCAAiC,EAAE,SAAS,GAAG,EAAE,MAAM,mCACxD;AAEH,QAAO,OAAO,OAAO;EACnB,UAAU,EAAE;EACZ,OAAO,EAAE;EACT,kBAAkB,EAAE;EACpB,mBAAmB,EAAE;EACrB,GAAI,OAAO,EAAE,0BAA0B,WACnC,EAAE,uBAAuB,EAAE,uBAAuB,GAClD,EAAE;EACN,GAAI,OAAO,EAAE,yBAAyB,WAClC,EAAE,sBAAsB,EAAE,sBAAsB,GAChD,EAAE;EACN,GAAI,OAAO,EAAE,WAAW,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,EAAE;EAC5D,GAAI,OAAO,EAAE,UAAU,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE;EAC1D,CAAC"}
1
+ {"version":3,"file":"refresh.js","names":["res: Awaited<ReturnType<typeof fetchImpl>>","raw: unknown"],"sources":["../src/refresh.ts"],"sourcesContent":["/**\n * `refreshPricing(...)` - opt-in network call. The framework never\n * invokes this automatically; it exists so the CLI (`graphorin\n * pricing refresh`) and operator scripts can pull a fresh snapshot\n * without re-installing the package.\n *\n * The function fetches the supplied URL as JSON and validates the\n * shape against the {@link PricingSnapshot} contract. The bundled\n * snapshot is **not** mutated; callers persist the refreshed\n * snapshot to disk and reload it explicitly.\n *\n * @packageDocumentation\n */\n\nimport { computeEntriesDigest } from './snapshot/bundled.js';\nimport type { ModelPrice, PricingSnapshot } from './types.js';\n\n/**\n * Configuration shape for {@link refreshPricing}. The `fetchImpl`\n * override exists so tests can exercise the function without making\n * a real network call.\n *\n * @stable\n */\nexport interface RefreshPricingOptions {\n /** Snapshot URL - typically the upstream pricing JSON. */\n readonly url: string;\n /** Optional headers (auth, conditional GET, etc.). */\n readonly headers?: Readonly<Record<string, string>>;\n /** Optional fetch override - useful in tests. */\n readonly fetchImpl?: typeof fetch;\n /** Override the snapshot date stamped on the result. Defaults to today. */\n readonly snapshotDate?: string;\n /** Override the snapshot version string. Defaults to `'graphorin/0.1+refreshed'`. */\n readonly version?: string;\n /**\n * Hard timeout for the network fetch in milliseconds. Default\n * `30000`. Aborts the request (and throws) if the upstream is slow\n * or unreachable so `graphorin pricing refresh` cannot hang. Pass an\n * explicit {@link RefreshPricingOptions.signal} to manage\n * cancellation yourself; the two are combined.\n */\n readonly timeoutMs?: number;\n /** Caller-supplied abort signal, combined with the timeout. */\n readonly signal?: AbortSignal;\n}\n\n/**\n * Pull a fresh snapshot from the supplied URL and return it. Network\n * failures and shape mismatches surface as thrown errors so the CLI\n * can surface them to the operator.\n *\n * @stable\n */\nexport async function refreshPricing(opts: RefreshPricingOptions): Promise<PricingSnapshot> {\n const fetchImpl = opts.fetchImpl ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new TypeError(\n 'refreshPricing: no fetch implementation available. Provide `fetchImpl` ' +\n 'explicitly or run on a Node.js version with global fetch (>= 18).',\n );\n }\n const timeoutMs = opts.timeoutMs ?? 30_000;\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal =\n opts.signal === undefined ? timeoutSignal : AbortSignal.any([opts.signal, timeoutSignal]);\n let res: Awaited<ReturnType<typeof fetchImpl>>;\n try {\n res = await fetchImpl(opts.url, { headers: opts.headers ?? {}, signal });\n } catch (err) {\n if (timeoutSignal.aborted) {\n throw new Error(`refreshPricing: timed out after ${timeoutMs} ms fetching ${opts.url}`, {\n cause: err,\n });\n }\n throw err;\n }\n if (!res.ok) {\n throw new Error(`refreshPricing: HTTP ${res.status} ${res.statusText} from ${opts.url}`);\n }\n const body = (await res.json()) as unknown;\n const entries = parseEntries(body);\n const snapshotDate = opts.snapshotDate ?? new Date().toISOString().slice(0, 10);\n const version = opts.version ?? 'graphorin/0.1+refreshed';\n return Object.freeze<PricingSnapshot>({\n version,\n source: opts.url,\n snapshotDate,\n currency: 'USD',\n sha256: computeEntriesDigest(entries),\n entries,\n });\n}\n\nfunction parseEntries(body: unknown): ReadonlyArray<ModelPrice> {\n if (body === null || typeof body !== 'object') {\n throw new Error('refreshPricing: response body is not an object');\n }\n // Two accepted shapes:\n // - `{ entries: ModelPrice[] }`\n // - `ModelPrice[]`\n let raw: unknown;\n if (Array.isArray(body)) {\n raw = body;\n } else {\n raw = (body as { entries?: unknown }).entries;\n if (!Array.isArray(raw)) {\n throw new Error('refreshPricing: response object is missing the `entries` array');\n }\n }\n const entries = (raw as ReadonlyArray<unknown>).map(parseEntry);\n return Object.freeze(entries);\n}\n\nfunction parseEntry(raw: unknown): ModelPrice {\n if (raw === null || typeof raw !== 'object') {\n throw new Error('refreshPricing: pricing entry is not an object');\n }\n const e = raw as Partial<ModelPrice>;\n if (typeof e.provider !== 'string' || typeof e.model !== 'string') {\n throw new Error('refreshPricing: pricing entry is missing provider / model');\n }\n if (typeof e.inputUsdPerToken !== 'number' || typeof e.outputUsdPerToken !== 'number') {\n throw new Error(\n `refreshPricing: pricing entry ${e.provider}/${e.model} is missing input / output prices`,\n );\n }\n return Object.freeze({\n provider: e.provider,\n model: e.model,\n inputUsdPerToken: e.inputUsdPerToken,\n outputUsdPerToken: e.outputUsdPerToken,\n ...(typeof e.cachedReadUsdPerToken === 'number'\n ? { cachedReadUsdPerToken: e.cachedReadUsdPerToken }\n : {}),\n ...(typeof e.cacheWriteUsdPerToken === 'number'\n ? { cacheWriteUsdPerToken: e.cacheWriteUsdPerToken }\n : {}),\n ...(typeof e.reasoningUsdPerToken === 'number'\n ? { reasoningUsdPerToken: e.reasoningUsdPerToken }\n : {}),\n ...(typeof e.region === 'string' ? { region: e.region } : {}),\n ...(typeof e.notes === 'string' ? { notes: e.notes } : {}),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAsDA,eAAsB,eAAe,MAAuD;CAC1F,MAAM,YAAY,KAAK,aAAa,WAAW;AAC/C,KAAI,OAAO,cAAc,WACvB,OAAM,IAAI,UACR,2IAED;CAEH,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,gBAAgB,YAAY,QAAQ,UAAU;CACpD,MAAM,SACJ,KAAK,WAAW,SAAY,gBAAgB,YAAY,IAAI,CAAC,KAAK,QAAQ,cAAc,CAAC;CAC3F,IAAIA;AACJ,KAAI;AACF,QAAM,MAAM,UAAU,KAAK,KAAK;GAAE,SAAS,KAAK,WAAW,EAAE;GAAE;GAAQ,CAAC;UACjE,KAAK;AACZ,MAAI,cAAc,QAChB,OAAM,IAAI,MAAM,mCAAmC,UAAU,eAAe,KAAK,OAAO,EACtF,OAAO,KACR,CAAC;AAEJ,QAAM;;AAER,KAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,IAAI,WAAW,QAAQ,KAAK,MAAM;CAG1F,MAAM,UAAU,aADF,MAAM,IAAI,MAAM,CACI;CAClC,MAAM,eAAe,KAAK,iCAAgB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;CAC/E,MAAM,UAAU,KAAK,WAAW;AAChC,QAAO,OAAO,OAAwB;EACpC;EACA,QAAQ,KAAK;EACb;EACA,UAAU;EACV,QAAQ,qBAAqB,QAAQ;EACrC;EACD,CAAC;;AAGJ,SAAS,aAAa,MAA0C;AAC9D,KAAI,SAAS,QAAQ,OAAO,SAAS,SACnC,OAAM,IAAI,MAAM,iDAAiD;CAKnE,IAAIC;AACJ,KAAI,MAAM,QAAQ,KAAK,CACrB,OAAM;MACD;AACL,QAAO,KAA+B;AACtC,MAAI,CAAC,MAAM,QAAQ,IAAI,CACrB,OAAM,IAAI,MAAM,iEAAiE;;CAGrF,MAAM,UAAW,IAA+B,IAAI,WAAW;AAC/D,QAAO,OAAO,OAAO,QAAQ;;AAG/B,SAAS,WAAW,KAA0B;AAC5C,KAAI,QAAQ,QAAQ,OAAO,QAAQ,SACjC,OAAM,IAAI,MAAM,iDAAiD;CAEnE,MAAM,IAAI;AACV,KAAI,OAAO,EAAE,aAAa,YAAY,OAAO,EAAE,UAAU,SACvD,OAAM,IAAI,MAAM,4DAA4D;AAE9E,KAAI,OAAO,EAAE,qBAAqB,YAAY,OAAO,EAAE,sBAAsB,SAC3E,OAAM,IAAI,MACR,iCAAiC,EAAE,SAAS,GAAG,EAAE,MAAM,mCACxD;AAEH,QAAO,OAAO,OAAO;EACnB,UAAU,EAAE;EACZ,OAAO,EAAE;EACT,kBAAkB,EAAE;EACpB,mBAAmB,EAAE;EACrB,GAAI,OAAO,EAAE,0BAA0B,WACnC,EAAE,uBAAuB,EAAE,uBAAuB,GAClD,EAAE;EACN,GAAI,OAAO,EAAE,0BAA0B,WACnC,EAAE,uBAAuB,EAAE,uBAAuB,GAClD,EAAE;EACN,GAAI,OAAO,EAAE,yBAAyB,WAClC,EAAE,sBAAsB,EAAE,sBAAsB,GAChD,EAAE;EACN,GAAI,OAAO,EAAE,WAAW,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,EAAE;EAC5D,GAAI,OAAO,EAAE,UAAU,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE;EAC1D,CAAC"}
@@ -2,8 +2,8 @@ import { ModelPrice, PricingSnapshot } from "../types.js";
2
2
 
3
3
  //#region src/snapshot/bundled.d.ts
4
4
 
5
- /** @internal used for `lookupPrice` defaults. */
6
- declare const SNAPSHOT_DATE = "2026-04-30";
5
+ /** @internal - used for `lookupPrice` defaults. */
6
+ declare const SNAPSHOT_DATE = "2026-07-04";
7
7
  /**
8
8
  * Compute a deterministic SHA-256 of the entries. Sorting the entry
9
9
  * keys before serialisation keeps the digest stable across Node
@@ -1 +1 @@
1
- {"version":3,"file":"bundled.d.ts","names":[],"sources":["../../src/snapshot/bundled.ts"],"sourcesContent":[],"mappings":";;;;;cAkBa,aAAA;;;;;;;;iBAoIG,oBAAA,UAA8B,cAAc;;;;;;;;cAsB/C,kBAAkB"}
1
+ {"version":3,"file":"bundled.d.ts","names":[],"sources":["../../src/snapshot/bundled.ts"],"sourcesContent":[],"mappings":";;;;;cAkBa,aAAA;;;;;;;;iBAiQG,oBAAA,UAA8B,cAAc;;;;;;;;cAsB/C,kBAAkB"}
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
 
3
3
  //#region src/snapshot/bundled.ts
4
4
  /**
5
- * Bundled pricing snapshot. The catalogue is intentionally small
5
+ * Bundled pricing snapshot. The catalogue is intentionally small -
6
6
  * operators wanting the full upstream catalogue should run the opt-in
7
7
  * `refreshPricing(...)` flow at build time and persist the result to
8
8
  * a custom snapshot file.
@@ -13,29 +13,121 @@ import { createHash } from "node:crypto";
13
13
  *
14
14
  * @packageDocumentation
15
15
  */
16
- /** @internal used for `lookupPrice` defaults. */
17
- const SNAPSHOT_DATE = "2026-04-30";
16
+ /** @internal - used for `lookupPrice` defaults. */
17
+ const SNAPSHOT_DATE = "2026-07-04";
18
18
  const ENTRIES = Object.freeze([
19
+ {
20
+ provider: "anthropic",
21
+ model: "claude-opus-4-5",
22
+ inputUsdPerToken: 5 / 1e6,
23
+ outputUsdPerToken: 25 / 1e6,
24
+ cachedReadUsdPerToken: .5 / 1e6,
25
+ cacheWriteUsdPerToken: 6.25 / 1e6
26
+ },
27
+ {
28
+ provider: "anthropic",
29
+ model: "claude-opus-4-1",
30
+ inputUsdPerToken: 15 / 1e6,
31
+ outputUsdPerToken: 75 / 1e6,
32
+ cachedReadUsdPerToken: 1.5 / 1e6,
33
+ cacheWriteUsdPerToken: 18.75 / 1e6
34
+ },
35
+ {
36
+ provider: "anthropic",
37
+ model: "claude-sonnet-4-5",
38
+ inputUsdPerToken: 3 / 1e6,
39
+ outputUsdPerToken: 15 / 1e6,
40
+ cachedReadUsdPerToken: .3 / 1e6,
41
+ cacheWriteUsdPerToken: 3.75 / 1e6,
42
+ notes: "Prompts <= 200k tokens; the long-context tier is priced higher."
43
+ },
44
+ {
45
+ provider: "anthropic",
46
+ model: "claude-haiku-4-5",
47
+ inputUsdPerToken: 1 / 1e6,
48
+ outputUsdPerToken: 5 / 1e6,
49
+ cachedReadUsdPerToken: .1 / 1e6,
50
+ cacheWriteUsdPerToken: 1.25 / 1e6
51
+ },
19
52
  {
20
53
  provider: "anthropic",
21
54
  model: "claude-3-5-sonnet-20241022",
22
55
  inputUsdPerToken: 3 / 1e6,
23
56
  outputUsdPerToken: 15 / 1e6,
24
- cachedReadUsdPerToken: .3 / 1e6
57
+ cachedReadUsdPerToken: .3 / 1e6,
58
+ cacheWriteUsdPerToken: 3.75 / 1e6
25
59
  },
26
60
  {
27
61
  provider: "anthropic",
28
62
  model: "claude-3-5-haiku-20241022",
29
63
  inputUsdPerToken: .8 / 1e6,
30
64
  outputUsdPerToken: 4 / 1e6,
31
- cachedReadUsdPerToken: .08 / 1e6
65
+ cachedReadUsdPerToken: .08 / 1e6,
66
+ cacheWriteUsdPerToken: 1 / 1e6
32
67
  },
33
68
  {
34
69
  provider: "anthropic",
35
70
  model: "claude-3-opus-20240229",
36
71
  inputUsdPerToken: 15 / 1e6,
37
72
  outputUsdPerToken: 75 / 1e6,
38
- cachedReadUsdPerToken: 1.5 / 1e6
73
+ cachedReadUsdPerToken: 1.5 / 1e6,
74
+ cacheWriteUsdPerToken: 18.75 / 1e6
75
+ },
76
+ {
77
+ provider: "openai",
78
+ model: "gpt-5",
79
+ inputUsdPerToken: 1.25 / 1e6,
80
+ outputUsdPerToken: 10 / 1e6,
81
+ cachedReadUsdPerToken: .125 / 1e6
82
+ },
83
+ {
84
+ provider: "openai",
85
+ model: "gpt-5-mini",
86
+ inputUsdPerToken: .25 / 1e6,
87
+ outputUsdPerToken: 2 / 1e6,
88
+ cachedReadUsdPerToken: .025 / 1e6
89
+ },
90
+ {
91
+ provider: "openai",
92
+ model: "gpt-5-nano",
93
+ inputUsdPerToken: .05 / 1e6,
94
+ outputUsdPerToken: .4 / 1e6,
95
+ cachedReadUsdPerToken: .005 / 1e6
96
+ },
97
+ {
98
+ provider: "openai",
99
+ model: "gpt-4.1",
100
+ inputUsdPerToken: 2 / 1e6,
101
+ outputUsdPerToken: 8 / 1e6,
102
+ cachedReadUsdPerToken: .5 / 1e6
103
+ },
104
+ {
105
+ provider: "openai",
106
+ model: "gpt-4.1-mini",
107
+ inputUsdPerToken: .4 / 1e6,
108
+ outputUsdPerToken: 1.6 / 1e6,
109
+ cachedReadUsdPerToken: .1 / 1e6
110
+ },
111
+ {
112
+ provider: "openai",
113
+ model: "gpt-4.1-nano",
114
+ inputUsdPerToken: .1 / 1e6,
115
+ outputUsdPerToken: .4 / 1e6,
116
+ cachedReadUsdPerToken: .025 / 1e6
117
+ },
118
+ {
119
+ provider: "openai",
120
+ model: "o3",
121
+ inputUsdPerToken: 2 / 1e6,
122
+ outputUsdPerToken: 8 / 1e6,
123
+ cachedReadUsdPerToken: .5 / 1e6
124
+ },
125
+ {
126
+ provider: "openai",
127
+ model: "o4-mini",
128
+ inputUsdPerToken: 1.1 / 1e6,
129
+ outputUsdPerToken: 4.4 / 1e6,
130
+ cachedReadUsdPerToken: .275 / 1e6
39
131
  },
40
132
  {
41
133
  provider: "openai",
@@ -65,6 +157,28 @@ const ENTRIES = Object.freeze([
65
157
  outputUsdPerToken: 4.4 / 1e6,
66
158
  cachedReadUsdPerToken: .55 / 1e6
67
159
  },
160
+ {
161
+ provider: "google",
162
+ model: "gemini-2.5-pro",
163
+ inputUsdPerToken: 1.25 / 1e6,
164
+ outputUsdPerToken: 10 / 1e6,
165
+ cachedReadUsdPerToken: .3125 / 1e6,
166
+ notes: "Prompts <= 200k tokens; the long-context tier is priced higher."
167
+ },
168
+ {
169
+ provider: "google",
170
+ model: "gemini-2.5-flash",
171
+ inputUsdPerToken: .3 / 1e6,
172
+ outputUsdPerToken: 2.5 / 1e6,
173
+ cachedReadUsdPerToken: .075 / 1e6
174
+ },
175
+ {
176
+ provider: "google",
177
+ model: "gemini-2.5-flash-lite",
178
+ inputUsdPerToken: .1 / 1e6,
179
+ outputUsdPerToken: .4 / 1e6,
180
+ cachedReadUsdPerToken: .025 / 1e6
181
+ },
68
182
  {
69
183
  provider: "google",
70
184
  model: "gemini-1.5-pro-002",
@@ -108,14 +222,14 @@ const ENTRIES = Object.freeze([
108
222
  model: "*",
109
223
  inputUsdPerToken: 0,
110
224
  outputUsdPerToken: 0,
111
- notes: "Local Ollama deployment operator-priced."
225
+ notes: "Local Ollama deployment - operator-priced."
112
226
  },
113
227
  {
114
228
  provider: "graphorin-llamacpp",
115
229
  model: "*",
116
230
  inputUsdPerToken: 0,
117
231
  outputUsdPerToken: 0,
118
- notes: "Local llama.cpp deployment operator-priced."
232
+ notes: "Local llama.cpp deployment - operator-priced."
119
233
  }
120
234
  ]);
121
235
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bundled.js","names":["ENTRIES: ReadonlyArray<ModelPrice>","out: Record<string, unknown>","BUNDLED_SNAPSHOT: PricingSnapshot"],"sources":["../../src/snapshot/bundled.ts"],"sourcesContent":["/**\n * Bundled pricing snapshot. The catalogue is intentionally small —\n * operators wanting the full upstream catalogue should run the opt-in\n * `refreshPricing(...)` flow at build time and persist the result to\n * a custom snapshot file.\n *\n * Prices are stored in **USD per token** (i.e. divide the per-million\n * upstream prices by 1_000_000). Numbers are accurate to the snapshot\n * date; consumers are expected to refresh on a regular cadence.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\n\nimport type { ModelPrice, PricingSnapshot } from '../types.js';\n\n/** @internal — used for `lookupPrice` defaults. */\nexport const SNAPSHOT_DATE = '2026-04-30';\n\nconst ENTRIES: ReadonlyArray<ModelPrice> = Object.freeze([\n // -----------------------------------------------------------------\n // Anthropic\n // -----------------------------------------------------------------\n {\n provider: 'anthropic',\n model: 'claude-3-5-sonnet-20241022',\n inputUsdPerToken: 3 / 1_000_000,\n outputUsdPerToken: 15 / 1_000_000,\n cachedReadUsdPerToken: 0.3 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-3-5-haiku-20241022',\n inputUsdPerToken: 0.8 / 1_000_000,\n outputUsdPerToken: 4 / 1_000_000,\n cachedReadUsdPerToken: 0.08 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-3-opus-20240229',\n inputUsdPerToken: 15 / 1_000_000,\n outputUsdPerToken: 75 / 1_000_000,\n cachedReadUsdPerToken: 1.5 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // OpenAI\n // -----------------------------------------------------------------\n {\n provider: 'openai',\n model: 'gpt-4o-2024-11-20',\n inputUsdPerToken: 2.5 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n cachedReadUsdPerToken: 1.25 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-4o-mini-2024-07-18',\n inputUsdPerToken: 0.15 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n cachedReadUsdPerToken: 0.075 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o1-2024-12-17',\n inputUsdPerToken: 15 / 1_000_000,\n outputUsdPerToken: 60 / 1_000_000,\n cachedReadUsdPerToken: 7.5 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o3-mini-2025-01-31',\n inputUsdPerToken: 1.1 / 1_000_000,\n outputUsdPerToken: 4.4 / 1_000_000,\n cachedReadUsdPerToken: 0.55 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Google\n // -----------------------------------------------------------------\n {\n provider: 'google',\n model: 'gemini-1.5-pro-002',\n inputUsdPerToken: 1.25 / 1_000_000,\n outputUsdPerToken: 5 / 1_000_000,\n cachedReadUsdPerToken: 0.3125 / 1_000_000,\n },\n {\n provider: 'google',\n model: 'gemini-1.5-flash-002',\n inputUsdPerToken: 0.075 / 1_000_000,\n outputUsdPerToken: 0.3 / 1_000_000,\n cachedReadUsdPerToken: 0.01875 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Mistral\n // -----------------------------------------------------------------\n {\n provider: 'mistral',\n model: 'mistral-large-2411',\n inputUsdPerToken: 2 / 1_000_000,\n outputUsdPerToken: 6 / 1_000_000,\n },\n {\n provider: 'mistral',\n model: 'mistral-small-2503',\n inputUsdPerToken: 0.2 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Cohere\n // -----------------------------------------------------------------\n {\n provider: 'cohere',\n model: 'command-r-plus-08-2024',\n inputUsdPerToken: 2.5 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n },\n {\n provider: 'cohere',\n model: 'command-r-08-2024',\n inputUsdPerToken: 0.15 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Local providers — documented as zero-cost so cost rollups still\n // include the call counts.\n // -----------------------------------------------------------------\n {\n provider: 'ollama',\n model: '*',\n inputUsdPerToken: 0,\n outputUsdPerToken: 0,\n notes: 'Local Ollama deployment — operator-priced.',\n },\n {\n provider: 'graphorin-llamacpp',\n model: '*',\n inputUsdPerToken: 0,\n outputUsdPerToken: 0,\n notes: 'Local llama.cpp deployment — operator-priced.',\n },\n] as const);\n\n/**\n * Compute a deterministic SHA-256 of the entries. Sorting the entry\n * keys before serialisation keeps the digest stable across Node\n * versions / object-property-iteration orderings.\n *\n * @internal\n */\nexport function computeEntriesDigest(entries: ReadonlyArray<ModelPrice>): string {\n const canonical = JSON.stringify(entries.map((entry) => sortKeys(entry as unknown)));\n return createHash('sha256').update(canonical, 'utf8').digest('hex');\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (Array.isArray(value)) return value.map(sortKeys);\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n out[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return out;\n}\n\n/**\n * The bundled snapshot. The `sha256` digest is computed over the\n * canonical JSON form of `entries` at module load time so consumers\n * can verify integrity without trusting the package metadata.\n *\n * @stable\n */\nexport const BUNDLED_SNAPSHOT: PricingSnapshot = Object.freeze({\n version: 'graphorin/0.1',\n source:\n 'https://github.com/o-stepper/graphorin/tree/main/packages/pricing/src/snapshot/bundled.ts',\n snapshotDate: SNAPSHOT_DATE,\n currency: 'USD',\n sha256: computeEntriesDigest(ENTRIES),\n entries: ENTRIES,\n});\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,gBAAgB;AAE7B,MAAMA,UAAqC,OAAO,OAAO;CAIvD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,KAAK;EACxB,uBAAuB,KAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,IAAI;EACvB,uBAAuB,MAAO;EAC/B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAK;EACvB,mBAAmB,KAAK;EACxB,uBAAuB,MAAM;EAC9B;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,KAAK;EACxB,uBAAuB,OAAO;EAC/B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,KAAM;EACzB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAK;EACvB,mBAAmB,KAAK;EACxB,uBAAuB,MAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB,uBAAuB,MAAO;EAC/B;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAO;EACzB,mBAAmB,IAAI;EACvB,uBAAuB,QAAS;EACjC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAQ;EAC1B,mBAAmB,KAAM;EACzB,uBAAuB,SAAU;EAClC;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,IAAI;EACxB;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,KAAM;EAC1B;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,KAAK;EACzB;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,KAAM;EAC1B;CAKD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB;EAClB,mBAAmB;EACnB,OAAO;EACR;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB;EAClB,mBAAmB;EACnB,OAAO;EACR;CACF,CAAU;;;;;;;;AASX,SAAgB,qBAAqB,SAA4C;CAC/E,MAAM,YAAY,KAAK,UAAU,QAAQ,KAAK,UAAU,SAAS,MAAiB,CAAC,CAAC;AACpF,QAAO,WAAW,SAAS,CAAC,OAAO,WAAW,OAAO,CAAC,OAAO,MAAM;;AAGrE,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,IAAI,SAAS;CACpD,MAAMC,MAA+B,EAAE;AACvC,MAAK,MAAM,OAAO,OAAO,KAAK,MAAiC,CAAC,MAAM,CACpE,KAAI,OAAO,SAAU,MAAkC,KAAK;AAE9D,QAAO;;;;;;;;;AAUT,MAAaC,mBAAoC,OAAO,OAAO;CAC7D,SAAS;CACT,QACE;CACF,cAAc;CACd,UAAU;CACV,QAAQ,qBAAqB,QAAQ;CACrC,SAAS;CACV,CAAC"}
1
+ {"version":3,"file":"bundled.js","names":["ENTRIES: ReadonlyArray<ModelPrice>","out: Record<string, unknown>","BUNDLED_SNAPSHOT: PricingSnapshot"],"sources":["../../src/snapshot/bundled.ts"],"sourcesContent":["/**\n * Bundled pricing snapshot. The catalogue is intentionally small -\n * operators wanting the full upstream catalogue should run the opt-in\n * `refreshPricing(...)` flow at build time and persist the result to\n * a custom snapshot file.\n *\n * Prices are stored in **USD per token** (i.e. divide the per-million\n * upstream prices by 1_000_000). Numbers are accurate to the snapshot\n * date; consumers are expected to refresh on a regular cadence.\n *\n * @packageDocumentation\n */\n\nimport { createHash } from 'node:crypto';\n\nimport type { ModelPrice, PricingSnapshot } from '../types.js';\n\n/** @internal - used for `lookupPrice` defaults. */\nexport const SNAPSHOT_DATE = '2026-07-04';\n\nconst ENTRIES: ReadonlyArray<ModelPrice> = Object.freeze([\n // -----------------------------------------------------------------\n // Anthropic - current families (core-provider-03). Dateless alias ids;\n // dated ids (`claude-haiku-4-5-20251001`) resolve through the lookup's\n // date-suffix fallback. Cache-write = 1.25x input (5-minute cache).\n // NOTE: the Claude 5 family (fable/mythos) and any tier released after\n // this snapshot date have NO entry on purpose - cost tracking reports\n // null + one WARN instead of an invented number. Refresh via\n // `refreshPricing(...)` or contribute the entry when pricing is public.\n // -----------------------------------------------------------------\n {\n provider: 'anthropic',\n model: 'claude-opus-4-5',\n inputUsdPerToken: 5 / 1_000_000,\n outputUsdPerToken: 25 / 1_000_000,\n cachedReadUsdPerToken: 0.5 / 1_000_000,\n cacheWriteUsdPerToken: 6.25 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-opus-4-1',\n inputUsdPerToken: 15 / 1_000_000,\n outputUsdPerToken: 75 / 1_000_000,\n cachedReadUsdPerToken: 1.5 / 1_000_000,\n cacheWriteUsdPerToken: 18.75 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-sonnet-4-5',\n inputUsdPerToken: 3 / 1_000_000,\n outputUsdPerToken: 15 / 1_000_000,\n cachedReadUsdPerToken: 0.3 / 1_000_000,\n cacheWriteUsdPerToken: 3.75 / 1_000_000,\n notes: 'Prompts <= 200k tokens; the long-context tier is priced higher.',\n },\n {\n provider: 'anthropic',\n model: 'claude-haiku-4-5',\n inputUsdPerToken: 1 / 1_000_000,\n outputUsdPerToken: 5 / 1_000_000,\n cachedReadUsdPerToken: 0.1 / 1_000_000,\n cacheWriteUsdPerToken: 1.25 / 1_000_000,\n },\n // Retired / legacy ids retained for historical cost attribution.\n {\n provider: 'anthropic',\n model: 'claude-3-5-sonnet-20241022',\n inputUsdPerToken: 3 / 1_000_000,\n outputUsdPerToken: 15 / 1_000_000,\n cachedReadUsdPerToken: 0.3 / 1_000_000,\n cacheWriteUsdPerToken: 3.75 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-3-5-haiku-20241022',\n inputUsdPerToken: 0.8 / 1_000_000,\n outputUsdPerToken: 4 / 1_000_000,\n cachedReadUsdPerToken: 0.08 / 1_000_000,\n cacheWriteUsdPerToken: 1 / 1_000_000,\n },\n {\n provider: 'anthropic',\n model: 'claude-3-opus-20240229',\n inputUsdPerToken: 15 / 1_000_000,\n outputUsdPerToken: 75 / 1_000_000,\n cachedReadUsdPerToken: 1.5 / 1_000_000,\n cacheWriteUsdPerToken: 18.75 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // OpenAI - cache reads are automatic (no breakpoints) and there is no\n // cache-write charge, so entries carry only `cachedReadUsdPerToken`.\n // -----------------------------------------------------------------\n {\n provider: 'openai',\n model: 'gpt-5',\n inputUsdPerToken: 1.25 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n cachedReadUsdPerToken: 0.125 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-5-mini',\n inputUsdPerToken: 0.25 / 1_000_000,\n outputUsdPerToken: 2 / 1_000_000,\n cachedReadUsdPerToken: 0.025 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-5-nano',\n inputUsdPerToken: 0.05 / 1_000_000,\n outputUsdPerToken: 0.4 / 1_000_000,\n cachedReadUsdPerToken: 0.005 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-4.1',\n inputUsdPerToken: 2 / 1_000_000,\n outputUsdPerToken: 8 / 1_000_000,\n cachedReadUsdPerToken: 0.5 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-4.1-mini',\n inputUsdPerToken: 0.4 / 1_000_000,\n outputUsdPerToken: 1.6 / 1_000_000,\n cachedReadUsdPerToken: 0.1 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-4.1-nano',\n inputUsdPerToken: 0.1 / 1_000_000,\n outputUsdPerToken: 0.4 / 1_000_000,\n cachedReadUsdPerToken: 0.025 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o3',\n inputUsdPerToken: 2 / 1_000_000,\n outputUsdPerToken: 8 / 1_000_000,\n cachedReadUsdPerToken: 0.5 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o4-mini',\n inputUsdPerToken: 1.1 / 1_000_000,\n outputUsdPerToken: 4.4 / 1_000_000,\n cachedReadUsdPerToken: 0.275 / 1_000_000,\n },\n // Retired / legacy ids retained for historical cost attribution.\n {\n provider: 'openai',\n model: 'gpt-4o-2024-11-20',\n inputUsdPerToken: 2.5 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n cachedReadUsdPerToken: 1.25 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'gpt-4o-mini-2024-07-18',\n inputUsdPerToken: 0.15 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n cachedReadUsdPerToken: 0.075 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o1-2024-12-17',\n inputUsdPerToken: 15 / 1_000_000,\n outputUsdPerToken: 60 / 1_000_000,\n cachedReadUsdPerToken: 7.5 / 1_000_000,\n },\n {\n provider: 'openai',\n model: 'o3-mini-2025-01-31',\n inputUsdPerToken: 1.1 / 1_000_000,\n outputUsdPerToken: 4.4 / 1_000_000,\n cachedReadUsdPerToken: 0.55 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Google - implicit-caching read discount; context-cache storage is\n // billed per hour upstream and is not modelled here.\n // -----------------------------------------------------------------\n {\n provider: 'google',\n model: 'gemini-2.5-pro',\n inputUsdPerToken: 1.25 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n cachedReadUsdPerToken: 0.3125 / 1_000_000,\n notes: 'Prompts <= 200k tokens; the long-context tier is priced higher.',\n },\n {\n provider: 'google',\n model: 'gemini-2.5-flash',\n inputUsdPerToken: 0.3 / 1_000_000,\n outputUsdPerToken: 2.5 / 1_000_000,\n cachedReadUsdPerToken: 0.075 / 1_000_000,\n },\n {\n provider: 'google',\n model: 'gemini-2.5-flash-lite',\n inputUsdPerToken: 0.1 / 1_000_000,\n outputUsdPerToken: 0.4 / 1_000_000,\n cachedReadUsdPerToken: 0.025 / 1_000_000,\n },\n // Retired / legacy ids retained for historical cost attribution.\n {\n provider: 'google',\n model: 'gemini-1.5-pro-002',\n inputUsdPerToken: 1.25 / 1_000_000,\n outputUsdPerToken: 5 / 1_000_000,\n cachedReadUsdPerToken: 0.3125 / 1_000_000,\n },\n {\n provider: 'google',\n model: 'gemini-1.5-flash-002',\n inputUsdPerToken: 0.075 / 1_000_000,\n outputUsdPerToken: 0.3 / 1_000_000,\n cachedReadUsdPerToken: 0.01875 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Mistral\n // -----------------------------------------------------------------\n {\n provider: 'mistral',\n model: 'mistral-large-2411',\n inputUsdPerToken: 2 / 1_000_000,\n outputUsdPerToken: 6 / 1_000_000,\n },\n {\n provider: 'mistral',\n model: 'mistral-small-2503',\n inputUsdPerToken: 0.2 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Cohere\n // -----------------------------------------------------------------\n {\n provider: 'cohere',\n model: 'command-r-plus-08-2024',\n inputUsdPerToken: 2.5 / 1_000_000,\n outputUsdPerToken: 10 / 1_000_000,\n },\n {\n provider: 'cohere',\n model: 'command-r-08-2024',\n inputUsdPerToken: 0.15 / 1_000_000,\n outputUsdPerToken: 0.6 / 1_000_000,\n },\n // -----------------------------------------------------------------\n // Local providers - documented as zero-cost so cost rollups still\n // include the call counts.\n // -----------------------------------------------------------------\n {\n provider: 'ollama',\n model: '*',\n inputUsdPerToken: 0,\n outputUsdPerToken: 0,\n notes: 'Local Ollama deployment - operator-priced.',\n },\n {\n provider: 'graphorin-llamacpp',\n model: '*',\n inputUsdPerToken: 0,\n outputUsdPerToken: 0,\n notes: 'Local llama.cpp deployment - operator-priced.',\n },\n] as const);\n\n/**\n * Compute a deterministic SHA-256 of the entries. Sorting the entry\n * keys before serialisation keeps the digest stable across Node\n * versions / object-property-iteration orderings.\n *\n * @internal\n */\nexport function computeEntriesDigest(entries: ReadonlyArray<ModelPrice>): string {\n const canonical = JSON.stringify(entries.map((entry) => sortKeys(entry as unknown)));\n return createHash('sha256').update(canonical, 'utf8').digest('hex');\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value;\n if (Array.isArray(value)) return value.map(sortKeys);\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n out[key] = sortKeys((value as Record<string, unknown>)[key]);\n }\n return out;\n}\n\n/**\n * The bundled snapshot. The `sha256` digest is computed over the\n * canonical JSON form of `entries` at module load time so consumers\n * can verify integrity without trusting the package metadata.\n *\n * @stable\n */\nexport const BUNDLED_SNAPSHOT: PricingSnapshot = Object.freeze({\n version: 'graphorin/0.1',\n source:\n 'https://github.com/o-stepper/graphorin/tree/main/packages/pricing/src/snapshot/bundled.ts',\n snapshotDate: SNAPSHOT_DATE,\n currency: 'USD',\n sha256: computeEntriesDigest(ENTRIES),\n entries: ENTRIES,\n});\n"],"mappings":";;;;;;;;;;;;;;;;AAkBA,MAAa,gBAAgB;AAE7B,MAAMA,UAAqC,OAAO,OAAO;CAUvD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,KAAK;EACxB,uBAAuB,KAAM;EAC7B,uBAAuB,OAAO;EAC/B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAK;EACvB,mBAAmB,KAAK;EACxB,uBAAuB,MAAM;EAC7B,uBAAuB,QAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,KAAK;EACxB,uBAAuB,KAAM;EAC7B,uBAAuB,OAAO;EAC9B,OAAO;EACR;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,IAAI;EACvB,uBAAuB,KAAM;EAC7B,uBAAuB,OAAO;EAC/B;CAED;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,KAAK;EACxB,uBAAuB,KAAM;EAC7B,uBAAuB,OAAO;EAC/B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,IAAI;EACvB,uBAAuB,MAAO;EAC9B,uBAAuB,IAAI;EAC5B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAK;EACvB,mBAAmB,KAAK;EACxB,uBAAuB,MAAM;EAC7B,uBAAuB,QAAQ;EAChC;CAKD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAO;EACzB,mBAAmB,KAAK;EACxB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,IAAI;EACvB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,KAAM;EACzB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,IAAI;EACvB,uBAAuB,KAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,MAAM;EACzB,uBAAuB,KAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,KAAM;EACzB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,IAAI;EACvB,uBAAuB,KAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB,uBAAuB,OAAQ;EAChC;CAED;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,KAAK;EACxB,uBAAuB,OAAO;EAC/B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,KAAM;EACzB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAK;EACvB,mBAAmB,KAAK;EACxB,uBAAuB,MAAM;EAC9B;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,MAAM;EACzB,uBAAuB,MAAO;EAC/B;CAKD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAO;EACzB,mBAAmB,KAAK;EACxB,uBAAuB,QAAS;EAChC,OAAO;EACR;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,MAAM;EACzB,uBAAuB,OAAQ;EAChC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,KAAM;EACzB,uBAAuB,OAAQ;EAChC;CAED;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAO;EACzB,mBAAmB,IAAI;EACvB,uBAAuB,QAAS;EACjC;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,OAAQ;EAC1B,mBAAmB,KAAM;EACzB,uBAAuB,SAAU;EAClC;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,IAAI;EACtB,mBAAmB,IAAI;EACxB;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,KAAM;EACxB,mBAAmB,KAAM;EAC1B;CAID;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAM;EACxB,mBAAmB,KAAK;EACzB;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB,MAAO;EACzB,mBAAmB,KAAM;EAC1B;CAKD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB;EAClB,mBAAmB;EACnB,OAAO;EACR;CACD;EACE,UAAU;EACV,OAAO;EACP,kBAAkB;EAClB,mBAAmB;EACnB,OAAO;EACR;CACF,CAAU;;;;;;;;AASX,SAAgB,qBAAqB,SAA4C;CAC/E,MAAM,YAAY,KAAK,UAAU,QAAQ,KAAK,UAAU,SAAS,MAAiB,CAAC,CAAC;AACpF,QAAO,WAAW,SAAS,CAAC,OAAO,WAAW,OAAO,CAAC,OAAO,MAAM;;AAGrE,SAAS,SAAS,OAAyB;AACzC,KAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,MAAM,IAAI,SAAS;CACpD,MAAMC,MAA+B,EAAE;AACvC,MAAK,MAAM,OAAO,OAAO,KAAK,MAAiC,CAAC,MAAM,CACpE,KAAI,OAAO,SAAU,MAAkC,KAAK;AAE9D,QAAO;;;;;;;;;AAUT,MAAaC,mBAAoC,OAAO,OAAO;CAC7D,SAAS;CACT,QACE;CACF,cAAc;CACd,UAAU;CACV,QAAQ,qBAAqB,QAAQ;CACrC,SAAS;CACV,CAAC"}
package/dist/types.d.ts CHANGED
@@ -21,6 +21,12 @@ interface ModelPrice {
21
21
  readonly outputUsdPerToken: number;
22
22
  /** Optional cached-read price (Anthropic / OpenAI prompt caching). */
23
23
  readonly cachedReadUsdPerToken?: number;
24
+ /**
25
+ * Optional cache-write (cache-creation) price. Anthropic bills prompt
26
+ * tokens written to the 5-minute cache at 1.25x the input rate; OpenAI
27
+ * does not charge (or report) cache writes, so its entries omit this.
28
+ */
29
+ readonly cacheWriteUsdPerToken?: number;
24
30
  /** Optional reasoning-token price (OpenAI o1 / Gemini 2 thinking). */
25
31
  readonly reasoningUsdPerToken?: number;
26
32
  /** Optional region label (e.g. `'us-east-1'`). */
@@ -60,6 +66,7 @@ interface LookupPriceResult {
60
66
  readonly inputUsdPerToken: number;
61
67
  readonly outputUsdPerToken: number;
62
68
  readonly cachedReadUsdPerToken?: number;
69
+ readonly cacheWriteUsdPerToken?: number;
63
70
  readonly reasoningUsdPerToken?: number;
64
71
  readonly source: string;
65
72
  readonly snapshotDate: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;AAiEA;AAcA;;;;;;AAgBA;UA/EiB,UAAA;;;;;;;;;;;;;;;;;;;;;;;UAwBA,eAAA;;;;;;oBAMG,cAAc;;;;;;;UAQjB,eAAA;;;;;;;;;;UAWA,iBAAA;;;;;;;;;;;;;UAcA,gBAAA;;;;oBAIG;mBACD;2BACQ,oBAAoB;;;;;;;;;UAU9B,oBAAA;uBACM,SAAS"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;AAuEA;AAeA;;;;;;AAgBA;UAtFiB,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA8BA,eAAA;;;;;;oBAMG,cAAc;;;;;;;UAQjB,eAAA;;;;;;;;;;UAWA,iBAAA;;;;;;;;;;;;;;UAeA,gBAAA;;;;oBAIG;mBACD;2BACQ,oBAAoB;;;;;;;;;UAU9B,oBAAA;uBACM,SAAS"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphorin/pricing",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Bundled LLM pricing snapshot for the Graphorin framework. Ships per-provider, per-model input / output / cached-read prices alongside provenance metadata, a `lookupPrice(...)` resolver, snapshot diffing utilities, an opt-in `refreshPricing(...)` hook (network gated; never automatic), and `listMissingModels(...)` for trace audits.",
5
5
  "license": "MIT",
6
6
  "author": "Oleksiy Stepurenko",
@@ -48,7 +48,7 @@
48
48
  "LICENSE"
49
49
  ],
50
50
  "dependencies": {
51
- "@graphorin/core": "0.5.0"
51
+ "@graphorin/core": "0.6.1"
52
52
  },
53
53
  "publishConfig": {
54
54
  "access": "public",