(pathname: string)
| 243 | * Replaces dynamic segments (model IDs, deployment names, etc.) with placeholders. |
| 244 | */ |
| 245 | export function normalizePathLabel(pathname: string): string { |
| 246 | // Bedrock: /model/{modelId}/{operation} |
| 247 | const bedrockMatch = pathname.match(BEDROCK_RE); |
| 248 | if (bedrockMatch) { |
| 249 | return `/model/{modelId}/${bedrockMatch[2]}`; |
| 250 | } |
| 251 | |
| 252 | // Gemini: /v1beta/models/{model}:{action} |
| 253 | const geminiMatch = pathname.match(GEMINI_RE); |
| 254 | if (geminiMatch) { |
| 255 | return `/v1beta/models/{model}:${geminiMatch[2]}`; |
| 256 | } |
| 257 | |
| 258 | // Azure: /openai/deployments/{id}/{operation} |
| 259 | const azureMatch = pathname.match(AZURE_RE); |
| 260 | if (azureMatch) { |
| 261 | return `/openai/deployments/{id}/${azureMatch[2]}`; |
| 262 | } |
| 263 | |
| 264 | // Vertex AI: /v1/projects/{p}/locations/{l}/publishers/google/models/{m}:{action} |
| 265 | const vertexMatch = pathname.match(VERTEX_RE); |
| 266 | if (vertexMatch) { |
| 267 | return `/v1/projects/{p}/locations/{l}/publishers/google/models/{m}:${vertexMatch[4]}`; |
| 268 | } |
| 269 | |
| 270 | // ElevenLabs TTS: /v1/text-to-speech/{voice_id} |
| 271 | if (ELEVENLABS_TTS_RE.test(pathname)) { |
| 272 | return "/v1/text-to-speech/{voice_id}"; |
| 273 | } |
| 274 | |
| 275 | // OpenRouter video: /api/v1/videos/{jobId}[/content] — jobIds are random |
| 276 | // UUIDs, so raw paths would mint unbounded label cardinality. The static |
| 277 | // /api/v1/videos/models listing route must not collapse into {jobId}. |
| 278 | if (OPENROUTER_VIDEO_CONTENT_RE.test(pathname)) { |
| 279 | return "/api/v1/videos/{jobId}/content"; |
| 280 | } |
| 281 | if (pathname !== "/api/v1/videos/models" && OPENROUTER_VIDEO_STATUS_RE.test(pathname)) { |
| 282 | return "/api/v1/videos/{jobId}"; |
| 283 | } |
| 284 | |
| 285 | // Google Veo video: submit `:predictLongRunning` + poll `/v1beta/operations/{name}`. |
| 286 | // Operation names are random UUIDs, so the raw path would mint unbounded |
| 287 | // label cardinality. |
| 288 | if (VEO_PREDICT_LRO_RE.test(pathname)) { |
| 289 | return "/v1beta/models/{model}:predictLongRunning"; |
| 290 | } |
| 291 | if (VEO_OPERATION_RE.test(pathname)) { |
| 292 | return "/v1beta/operations/{name}"; |
| 293 | } |
| 294 | |
| 295 | // xAI Grok Imagine submit is a static literal path — keep it distinct from |
| 296 | // the `/v1/videos/{id}` status label below (which it would otherwise collapse |
| 297 | // into), exactly as `/api/v1/videos/models` is kept out of the jobId bucket. |
| 298 | if (pathname === GROK_VIDEO_SUBMIT_PATH) { |
| 299 | return GROK_VIDEO_SUBMIT_PATH; |
| 300 | } |
| 301 | |
| 302 | // OpenAI/Grok video status: /v1/videos/{id} |
no outgoing calls
no test coverage detected
searching dependent graphs…