@oh-my-pi/pi-catalog 17.1.7 → 17.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.1.8] - 2026-07-28
6
+
7
+ ### Added
8
+
9
+ - Added `resolveVertexEndpointHost(location)` utility to resolve the correct Vertex AI API endpoint hostnames for global, multi-region, and regional locations.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed an issue where `calculateCost` under-reported Anthropic cache-write costs by honoring the `usage.cttl` breakdown to correctly price 1-hour retention writes at 2x the base input rate.
14
+
5
15
  ## [17.1.7] - 2026-07-27
6
16
 
7
17
  ### Added
@@ -136,6 +136,19 @@ export declare function modelMatchesHost(model: {
136
136
  provider: string;
137
137
  baseUrl: string;
138
138
  }, host: KnownHost): boolean;
139
+ /**
140
+ * Hostname for a Vertex AI GenerateContent / rawPredict / OpenAI-compat
141
+ * request for the given location.
142
+ *
143
+ * - `global` → global endpoint (`aiplatform.googleapis.com`)
144
+ * - `eu` / `us` multi-regions → REP endpoints (`aiplatform.{eu|us}.rep.googleapis.com`)
145
+ * - every other location → regional (`{location}-aiplatform.googleapis.com`)
146
+ *
147
+ * Multi-region codes do NOT follow the regional `{location}-aiplatform` pattern;
148
+ * interpolating them that way yields hosts like `eu-aiplatform.googleapis.com`
149
+ * that 404.
150
+ */
151
+ export declare function resolveVertexEndpointHost(location: string): string;
139
152
  /** Vertex AI express-mode OpenAI-compatible endpoint (`…/endpoints/openapi`). */
140
153
  export declare function isVertexExpressOpenAIUrl(baseUrl: string): boolean;
141
154
  /** Vertex AI Anthropic raw-predict endpoints (`:streamRawPredict` / `:rawPredict`). */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "17.1.7",
4
+ "version": "17.1.8",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -34,12 +34,12 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.1",
37
- "@oh-my-pi/pi-utils": "17.1.7",
37
+ "@oh-my-pi/pi-utils": "17.1.8",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "17.1.7",
42
+ "@oh-my-pi/pi-ai": "17.1.8",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
package/src/hosts.ts CHANGED
@@ -111,6 +111,24 @@ function includesAsciiCaseInsensitive(value: string, lowerNeedle: string): boole
111
111
 
112
112
  // --- Endpoint-shape predicates (URL path/verb shapes, not vendor hosts) ---
113
113
 
114
+ /**
115
+ * Hostname for a Vertex AI GenerateContent / rawPredict / OpenAI-compat
116
+ * request for the given location.
117
+ *
118
+ * - `global` → global endpoint (`aiplatform.googleapis.com`)
119
+ * - `eu` / `us` multi-regions → REP endpoints (`aiplatform.{eu|us}.rep.googleapis.com`)
120
+ * - every other location → regional (`{location}-aiplatform.googleapis.com`)
121
+ *
122
+ * Multi-region codes do NOT follow the regional `{location}-aiplatform` pattern;
123
+ * interpolating them that way yields hosts like `eu-aiplatform.googleapis.com`
124
+ * that 404.
125
+ */
126
+ export function resolveVertexEndpointHost(location: string): string {
127
+ if (location === "global") return "aiplatform.googleapis.com";
128
+ if (location === "eu" || location === "us") return `aiplatform.${location}.rep.googleapis.com`;
129
+ return `${location}-aiplatform.googleapis.com`;
130
+ }
131
+
114
132
  /** Vertex AI express-mode OpenAI-compatible endpoint (`…/endpoints/openapi`). */
115
133
  export function isVertexExpressOpenAIUrl(baseUrl: string): boolean {
116
134
  return baseUrl.includes("/endpoints/openapi");
package/src/models.ts CHANGED
@@ -48,10 +48,38 @@ export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage
48
48
  usage.cost.input = (model.cost.input / 1000000) * (usage.input + (orchestration?.input ?? 0));
49
49
  usage.cost.output = (model.cost.output / 1000000) * (usage.output + (orchestration?.output ?? 0));
50
50
  usage.cost.cacheRead = (model.cost.cacheRead / 1000000) * (usage.cacheRead + (orchestration?.cacheRead ?? 0));
51
- usage.cost.cacheWrite = (model.cost.cacheWrite / 1000000) * usage.cacheWrite;
51
+ usage.cost.cacheWrite = cacheWriteCost(model, usage);
52
52
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
53
53
  return usage.cost;
54
54
  }
55
+
56
+ /**
57
+ * Price cache-write tokens, honoring the TTL breakdown when the provider reports one.
58
+ *
59
+ * `model.cost.cacheWrite` is the 5-minute write rate (Anthropic bills 5m writes at
60
+ * 1.25x base input). When `usage.cttl` is present the write mixes 5m and 1h
61
+ * breakpoints — omp defaults to 1h retention on first-party Anthropic, and 1h writes
62
+ * bill at 2x base input — so each component is priced at its own rate instead of the
63
+ * flat 5m rate. Deriving 1h from `input * 2` (Anthropic's published multiplier) is
64
+ * model-independent and stays correct even for legacy entries whose stored
65
+ * `cacheWrite` scalar drifts from 1.25x input. Providers that omit `cttl`
66
+ * (everyone but Anthropic) keep the flat-rate calculation.
67
+ *
68
+ * The breakdown is documented to sum to `usage.cacheWrite`, but the two are written
69
+ * from independent wire fields (`cache_creation` vs `cache_creation_input_tokens`),
70
+ * so any unattributed remainder is priced at the flat rate instead of being dropped:
71
+ * a partial or stale breakdown must never make write tokens free.
72
+ */
73
+ function cacheWriteCost<TApi extends Api>(model: Model<TApi>, usage: Usage): number {
74
+ const rate5m = model.cost.cacheWrite / 1000000;
75
+ const cttl = usage.cttl;
76
+ if (!cttl) return rate5m * usage.cacheWrite;
77
+ const fiveMinute = cttl.ephemeral5m ?? 0;
78
+ const oneHour = cttl.ephemeral1h ?? 0;
79
+ const residual = Math.max(0, usage.cacheWrite - fiveMinute - oneHour);
80
+ return rate5m * (fiveMinute + residual) + ((model.cost.input * 2) / 1000000) * oneHour;
81
+ }
82
+
55
83
  /**
56
84
  * Check if two models are equal by comparing both their id and provider.
57
85
  * Returns false if either model is null or undefined.