@seanhogg/builderforce-sdk 0.9.0 → 2026.6.30

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/README.md CHANGED
@@ -173,6 +173,25 @@ const strict = await client.chat.completions.create({
173
173
  const retries = strict._builderforce?.schemaRetries ?? 0;
174
174
  ```
175
175
 
176
+ ### Avoiding `schema_too_complex` — `deriveResponseFormat`
177
+
178
+ A strict `json_schema` gives the best conformance, but some vendors' constrained-decoding engines reject a schema that's too complex (Gemini's *"too many states for serving"*). When **every** candidate model rejects it, the gateway returns a terminal `422 schema_too_complex` (see [Errors](#errors)) rather than burning the whole cascade and mislabelling it `429`. The cleaner fix is to not send a strict schema a vendor can't honour — `deriveResponseFormat` is the pre-flight guard: it emits strict `json_schema` when the schema is within a complexity ceiling, and falls back to loose `json_object` when it isn't.
179
+
180
+ ```ts
181
+ import { deriveResponseFormat } from '@seanhogg/builderforce-sdk';
182
+
183
+ // `schema` is a plain JSON-Schema object. Using Zod? Convert first:
184
+ // import { zodToJsonSchema } from 'zod-to-json-schema';
185
+ // const schema = zodToJsonSchema(MyZodSchema);
186
+ const response_format = deriveResponseFormat(schema, { name: 'JobExtract' });
187
+ // → { type: 'json_schema', json_schema: { name, schema, strict: true } } when simple enough
188
+ // → { type: 'json_object' } when too complex
189
+
190
+ const res = await client.chat.completions.create({ response_format, messages });
191
+ ```
192
+
193
+ Routing is gateway-owned, so omit `vendor` and the conservative cross-vendor ceiling applies (the schema is accepted whichever vendor serves it). Pass `{ vendor: 'googleai' }` to check against that vendor's specific ceiling when you've pinned a `model`, or `{ maxComplexity }` to override. `canUseStrictSchema(schema, opts)` and `estimateSchemaComplexity(schema)` are exported too if you want to branch or log the downgrade yourself.
194
+
176
195
  ## Vision — image + text in one message
177
196
 
178
197
  ```ts
@@ -339,6 +358,7 @@ try {
339
358
  | 409 | `idempotent_replay` | `Idempotency-Key` was used within the last 10 min — treat as no-op |
340
359
  | 429 | `plan_token_limit_exceeded` | Tenant hit daily plan budget. **`error.terminal === true`** — don't retry on a different model. |
341
360
  | 429 | `claw_token_limit_exceeded` | Per-claw daily cap exceeded (`clk_*` keys only). **`error.terminal === true`** — same caveat. |
361
+ | 422 | `schema_too_complex` | Every candidate model rejected `response_format.json_schema` as too complex for its constrained-decoding engine. **`error.terminal === true`** — a different model won't help; simplify the schema or use `json_object` (see [`deriveResponseFormat`](#avoiding-schema_too_complex--deriveresponseformat)). |
342
362
  | 403 | `origin_not_authorized` | Browser request from an origin not in the key's allowlist (or key has no allowlist — server-only) |
343
363
  | 403 | `strict_pin_not_allowed` | `modelStrict: true` requested on a free tenant without a superadmin daily-limit override — upgrade or drop `modelStrict`. |
344
364
  | 503 | `model_unavailable` | `modelStrict: true` and the requested model is on cooldown / unconfigured. `error.details = { requestedModel, reason }`. |
@@ -404,18 +424,55 @@ For lighter triage you don't need to quote the trace ID at all — the failover
404
424
 
405
425
  ```ts
406
426
  for (const f of res._builderforce?.failovers ?? []) {
407
- console.log(f.vendor, f.model, f.code, f.kind, `${f.durationMs}ms`);
408
- // e.g. openrouter qwen/qwen3-coder:free 429 rate_limit 1034ms
427
+ console.log(f.vendor, f.model, f.code, f.kind, f.reason, f.upstreamStatus, `${f.durationMs}ms`);
428
+ // e.g. openrouter qwen/qwen3-coder:free 429 rate_limit - - 1034ms
429
+ // e.g. googleai gemini-2.5-flash 422 schema schema_too_complex 400 210ms
409
430
  }
410
431
  ```
411
432
 
412
433
  | Field | Meaning |
413
434
  |---|---|
414
435
  | `durationMs` | Wall-clock time the gateway spent on that attempt. A `25000`-ish value with `kind: 'timeout'` means the vendor hung. |
415
- | `kind` | `'rate_limit' \| 'timeout' \| 'auth' \| 'server_error' \| 'client_error' \| 'network' \| 'skipped'`. Roll these up to spot single-vendor saturation vs a broad outage. |
436
+ | `kind` | `'rate_limit' \| 'timeout' \| 'auth' \| 'server_error' \| 'client_error' \| 'schema' \| 'content_filter' \| 'network' \| 'skipped'`. Roll these up to spot single-vendor saturation vs a broad outage. Open union — newer gateways may add classes. |
437
+ | `reason` | Stable machine-readable cause slug when one applies (e.g. `'schema_too_complex'`). **Branch on this, not on the message string.** |
438
+ | `upstreamStatus` | The REAL upstream HTTP status before the gateway normalized it into `code` — e.g. a Gemini schema 400 surfaces as `code: 422` with `upstreamStatus: 400`. |
416
439
 
417
440
  The **full upstream error text** for each attempt is deliberately *not* included in `failovers` — it can contain raw provider payloads. It's recorded against the trace and is only visible server-side; quote the trace ID to see it.
418
441
 
442
+ ### `classifyError` — one authoritative classifier
443
+
444
+ Rather than each consumer hand-rolling a `429/408/401/5xx → kind` switch (which drifts), the SDK ships a first-party classifier keyed off the gateway's **own** failure taxonomy (`error.code` + `error.terminal` + the failover breakdown):
445
+
446
+ ```ts
447
+ import { classifyError } from '@seanhogg/builderforce-sdk';
448
+
449
+ try {
450
+ return await client.chat.completions.create({ model: attempt.model, messages });
451
+ } catch (err) {
452
+ const c = classifyError(err); // works on ANY caught value (incl. network / AbortError)
453
+ if (c.terminal) throw err; // schema_too_complex, token_cap, auth, invalid_request → stop the chain
454
+ if (c.retryable) { // rate_limit, timeout, service_unavailable, network → safe to retry
455
+ if (c.retryAfter) await sleep(c.retryAfter * 1000);
456
+ continue; // try the next model in your profile
457
+ }
458
+ throw err;
459
+ }
460
+ ```
461
+
462
+ `classifyError(err)` returns `{ kind, terminal, retryable, retryAfter?, status?, code?, message }`. `kind` is one of `rate_limit | token_cap | schema_too_complex | invalid_request | auth | model_unavailable | timeout | service_unavailable | content_filter | network | aborted | unknown`. It subsumes the *Don't retry terminal errors* pattern above — `terminal` already folds in token-cap exhaustion, schema rejections, auth, and malformed requests.
463
+
464
+ ## Billing & retry semantics
465
+
466
+ So you can reconcile your own tenant-key spend against a user-side ledger without ambiguity:
467
+
468
+ - **You are billed for the *winning* attempt only.** A successful response's `usage` (and the metered `llm_usage_log` row) reflects the tokens of the single model that actually returned the answer — **not** the sum of the cascade. The model is `_builderforce.resolvedModel`.
469
+ - **Failed-but-retried upstream attempts are *not* billed.** Every model the gateway tried and that failed over (each entry in `_builderforce.failovers`) cost you nothing — a 429/timeout/`schema` attempt produces no usage row. `retries` / `failovers.length` are diagnostic, not billable.
470
+ - **No hidden gateway-internal retry tokens.** `json_schema` conformance retries (`_builderforce.schemaRetries`) move to a *different* model on the chain; you're still billed only for the one whose output was accepted, not for each rejected draft.
471
+ - **Reliability-floor / overflow calls are flagged.** When a saturated pool falls through to a model Builderforce funds on its own keys, the row is marked `paid_overflow` (and counts against your daily overflow cap) — it's still one winning attempt, just metered against a different budget line. A call served by a tenant-connected Claude subscription is **not** metered as overflow ($0 to us).
472
+ - **Streaming:** the usage row is written from the final SSE chunk's `usage` once the stream completes; same "winning attempt only" rule.
473
+
474
+ Reconcile with `client.usage.get({ days, detail: true })` (row-level) — each row carries `useCase`, `metadata`, `idempotencyKey`, the resolved model, and token counts, so a join against your own table is exact.
475
+
419
476
  ## Models and usage
420
477
 
421
478
  ```ts
package/dist/index.cjs CHANGED
@@ -23,9 +23,14 @@ __export(index_exports, {
23
23
  BuilderforceApiError: () => BuilderforceApiError,
24
24
  BuilderforceClient: () => BuilderforceClient,
25
25
  ChatCompletionStream: () => ChatCompletionStream,
26
+ DEFAULT_SCHEMA_COMPLEXITY_CEILING: () => DEFAULT_SCHEMA_COMPLEXITY_CEILING,
26
27
  EmbeddingsApi: () => EmbeddingsApi,
27
28
  ImagesApi: () => ImagesApi,
28
- ModelsApi: () => ModelsApi
29
+ ModelsApi: () => ModelsApi,
30
+ canUseStrictSchema: () => canUseStrictSchema,
31
+ classifyError: () => classifyError,
32
+ deriveResponseFormat: () => deriveResponseFormat,
33
+ estimateSchemaComplexity: () => estimateSchemaComplexity
29
34
  });
30
35
  module.exports = __toCommonJS(index_exports);
31
36
 
@@ -289,7 +294,9 @@ var BuilderforceApiError = class extends Error {
289
294
  vendor: e.vendor,
290
295
  code: e.code,
291
296
  ...typeof ev.durationMs === "number" ? { durationMs: ev.durationMs } : {},
292
- ...typeof ev.kind === "string" ? { kind: ev.kind } : {}
297
+ ...typeof ev.kind === "string" ? { kind: ev.kind } : {},
298
+ ...typeof ev.reason === "string" ? { reason: ev.reason } : {},
299
+ ...typeof ev.upstreamStatus === "number" ? { upstreamStatus: ev.upstreamStatus } : {}
293
300
  });
294
301
  }
295
302
  }
@@ -468,13 +475,174 @@ var BuilderforceClient = class {
468
475
  this.usage = new UsageApi(http);
469
476
  }
470
477
  };
478
+
479
+ // src/application/classifyError.ts
480
+ var TOKEN_CAP_CODES = /* @__PURE__ */ new Set([
481
+ "plan_token_limit_exceeded",
482
+ "plan_monthly_token_limit_exceeded",
483
+ "agent_host_token_limit_exceeded",
484
+ "claw_token_limit_exceeded",
485
+ "image_credit_limit_exceeded"
486
+ ]);
487
+ function classifyError(err) {
488
+ if (!(err instanceof BuilderforceApiError)) {
489
+ const name = err?.name;
490
+ const message2 = err instanceof Error ? err.message : String(err);
491
+ if (name === "AbortError") {
492
+ return { kind: "aborted", terminal: true, retryable: false, message: message2 };
493
+ }
494
+ if (err instanceof TypeError) {
495
+ return { kind: "network", terminal: false, retryable: true, message: message2 };
496
+ }
497
+ return { kind: "unknown", terminal: false, retryable: false, message: message2 };
498
+ }
499
+ const { status, code, terminal, retryAfter, message } = err;
500
+ const base = {
501
+ ...retryAfter !== void 0 ? { retryAfter } : {},
502
+ ...status !== void 0 ? { status } : {},
503
+ ...code !== void 0 ? { code } : {},
504
+ message
505
+ };
506
+ if (code === "schema_too_complex" || lastFailoverReason(err) === "schema_too_complex") {
507
+ return { kind: "schema_too_complex", terminal: terminal ?? true, retryable: false, ...base };
508
+ }
509
+ if (code && TOKEN_CAP_CODES.has(code)) {
510
+ return { kind: "token_cap", terminal: terminal ?? true, retryable: false, ...base };
511
+ }
512
+ if (code === "model_unavailable") {
513
+ return { kind: "model_unavailable", terminal: terminal ?? false, retryable: false, ...base };
514
+ }
515
+ if (code === "worker_subrequest_exhausted") {
516
+ return { kind: "service_unavailable", terminal: false, retryable: true, ...base };
517
+ }
518
+ if (code === "aborted") {
519
+ return { kind: "aborted", terminal: true, retryable: false, ...base };
520
+ }
521
+ if (code === "content_filter") {
522
+ return { kind: "content_filter", terminal: terminal ?? true, retryable: false, ...base };
523
+ }
524
+ if (status === 408 || code === "timeout") {
525
+ return { kind: "timeout", terminal: false, retryable: true, ...base };
526
+ }
527
+ if (status === 401 || status === 403) {
528
+ return { kind: "auth", terminal: terminal ?? true, retryable: false, ...base };
529
+ }
530
+ if (status === 429) {
531
+ return { kind: "rate_limit", terminal: terminal ?? false, retryable: !(terminal ?? false), ...base };
532
+ }
533
+ if (status === 400 || status === 422) {
534
+ return { kind: "invalid_request", terminal: terminal ?? true, retryable: false, ...base };
535
+ }
536
+ if (status === 503) {
537
+ return { kind: "service_unavailable", terminal: false, retryable: true, ...base };
538
+ }
539
+ if (status !== void 0 && status >= 500) {
540
+ return { kind: "service_unavailable", terminal: false, retryable: true, ...base };
541
+ }
542
+ return { kind: "unknown", terminal: terminal ?? false, retryable: false, ...base };
543
+ }
544
+ function lastFailoverReason(err) {
545
+ const f = err.failovers;
546
+ if (!f || f.length === 0) return void 0;
547
+ return f[f.length - 1]?.reason;
548
+ }
549
+
550
+ // src/application/deriveResponseFormat.ts
551
+ var DEFAULT_SCHEMA_COMPLEXITY_CEILING = 80;
552
+ var VENDOR_SCHEMA_CEILINGS = {
553
+ // Low constrained-decoding ceiling — the vendor that motivated this guard.
554
+ googleai: 60,
555
+ google: 60,
556
+ gemini: 60,
557
+ // High-ceiling, robust strict-schema vendors.
558
+ openai: 600,
559
+ anthropic: 600,
560
+ cerebras: 300,
561
+ nvidia: 300,
562
+ openrouter: 200
563
+ };
564
+ var MAX_SCHEMA_WALK_DEPTH = 64;
565
+ function estimateSchemaComplexity(schema) {
566
+ let nodes = 0;
567
+ let totalEnumValues = 0;
568
+ let maxDepth = 0;
569
+ const walk = (node, depth) => {
570
+ if (depth > MAX_SCHEMA_WALK_DEPTH || node === null || typeof node !== "object") return;
571
+ if (depth > maxDepth) maxDepth = depth;
572
+ const s = node;
573
+ const enumVals = s["enum"];
574
+ if (Array.isArray(enumVals)) totalEnumValues += enumVals.length;
575
+ const props = s["properties"];
576
+ if (props && typeof props === "object") {
577
+ for (const key of Object.keys(props)) {
578
+ nodes += 1;
579
+ walk(props[key], depth + 1);
580
+ }
581
+ }
582
+ const items = s["items"];
583
+ if (Array.isArray(items)) items.forEach((it) => {
584
+ nodes += 1;
585
+ walk(it, depth + 1);
586
+ });
587
+ else if (items && typeof items === "object") {
588
+ nodes += 1;
589
+ walk(items, depth + 1);
590
+ }
591
+ for (const comb of ["anyOf", "oneOf", "allOf"]) {
592
+ const arr = s[comb];
593
+ if (Array.isArray(arr)) arr.forEach((sub) => {
594
+ nodes += 1;
595
+ walk(sub, depth + 1);
596
+ });
597
+ }
598
+ for (const defsKey of ["$defs", "definitions"]) {
599
+ const defs = s[defsKey];
600
+ if (defs && typeof defs === "object") {
601
+ for (const key of Object.keys(defs)) walk(defs[key], depth + 1);
602
+ }
603
+ }
604
+ };
605
+ walk(schema, 0);
606
+ return { nodes, maxDepth, totalEnumValues, score: nodes + totalEnumValues };
607
+ }
608
+ function ceilingFor(opts) {
609
+ if (opts?.maxComplexity != null && opts.maxComplexity >= 0) return opts.maxComplexity;
610
+ if (opts?.vendor) {
611
+ const v = opts.vendor.toLowerCase();
612
+ if (v in VENDOR_SCHEMA_CEILINGS) return VENDOR_SCHEMA_CEILINGS[v];
613
+ }
614
+ return DEFAULT_SCHEMA_COMPLEXITY_CEILING;
615
+ }
616
+ function canUseStrictSchema(schema, opts) {
617
+ const ceiling = ceilingFor(opts);
618
+ if (ceiling === 0) return false;
619
+ return estimateSchemaComplexity(schema).score <= ceiling;
620
+ }
621
+ function deriveResponseFormat(schema, opts) {
622
+ if (!canUseStrictSchema(schema, opts)) {
623
+ return { type: "json_object" };
624
+ }
625
+ return {
626
+ type: "json_schema",
627
+ json_schema: {
628
+ name: opts?.name ?? "response",
629
+ schema,
630
+ strict: opts?.strict ?? true
631
+ }
632
+ };
633
+ }
471
634
  // Annotate the CommonJS export names for ESM import in node:
472
635
  0 && (module.exports = {
473
636
  BuilderforceApiError,
474
637
  BuilderforceClient,
475
638
  ChatCompletionStream,
639
+ DEFAULT_SCHEMA_COMPLEXITY_CEILING,
476
640
  EmbeddingsApi,
477
641
  ImagesApi,
478
- ModelsApi
642
+ ModelsApi,
643
+ canUseStrictSchema,
644
+ classifyError,
645
+ deriveResponseFormat,
646
+ estimateSchemaComplexity
479
647
  });
480
648
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.ts","../src/application/ImagesApi.ts","../src/application/ModelsApi.ts","../src/application/UsageApi.ts","../src/infrastructure/httpClient.ts","../src/BuilderforceClient.ts"],"sourcesContent":["export { BuilderforceClient, type BuilderforceClientOptions } from './BuilderforceClient';\n\nexport type {\n // Roles & content\n ChatRole,\n ChatMessage,\n ContentPart,\n TextContentPart,\n ImageUrlContentPart,\n // Tool calling\n ToolSpec,\n ToolCall,\n ToolCallFunction,\n ToolCallDelta,\n ToolChoice,\n FunctionDefinition,\n // Structured output\n ResponseFormat,\n JsonSchemaSpec,\n // Per-call options\n PerCallOptions,\n // Chat completions\n ChatCompletionCreateParams,\n ChatCompletionChunk,\n ChatCompletionResponse,\n FailoverEvent,\n // Models / usage\n AiCapability,\n ModelInfo,\n ModelsListResponse,\n UsageByModel,\n UsageByDay,\n UsageByUser,\n UsageResponse,\n UsageGetParams,\n // Embeddings\n EmbeddingsCreateParams,\n EmbeddingsResponse,\n EmbeddingObject,\n // Image generation\n ImageGenerationCreateParams,\n ImageGenerationResponse,\n ImageGenerationDataEntry,\n} from './domain/types';\n\nexport { ChatCompletionStream } from './application/ChatCompletionsApi';\nexport { EmbeddingsApi } from './application/EmbeddingsApi';\nexport { ImagesApi } from './application/ImagesApi';\nexport { ModelsApi } from './application/ModelsApi';\nexport { BuilderforceApiError } from './infrastructure/httpClient';\n","export async function* parseSseJson<T>(\n stream: ReadableStream<Uint8Array>,\n): AsyncGenerator<T, void, unknown> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data: ')) continue;\n\n const data = trimmed.slice(6).trim();\n if (data === '[DONE]') return;\n\n try {\n yield JSON.parse(data) as T;\n } catch {\n // Skip malformed chunks instead of breaking the stream.\n }\n }\n }\n}\n","import type { ChatCompletionChunk, ChatCompletionCreateParams, ChatCompletionResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\nimport { parseSseJson } from '../infrastructure/sse';\n\nexport class ChatCompletionStream implements AsyncIterable<ChatCompletionChunk> {\n private readonly stream: ReadableStream<Uint8Array>;\n\n constructor(stream: ReadableStream<Uint8Array>) {\n this.stream = stream;\n }\n\n [Symbol.asyncIterator](): AsyncIterator<ChatCompletionChunk, void, unknown> {\n return parseSseJson<ChatCompletionChunk>(this.stream);\n }\n\n async toText(): Promise<string> {\n let full = '';\n for await (const chunk of this) {\n const delta = chunk.choices?.[0]?.delta?.content;\n if (typeof delta === 'string') {\n full += delta;\n }\n }\n return full;\n }\n}\n\n/**\n * Pull SDK-level transport options (timeout, signal, idempotency key) out of\n * the params object so they don't get JSON-serialized into the request body.\n * Returns the request options AND the cleaned-up body.\n */\nfunction splitTransportOptions(params: ChatCompletionCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\nexport class ChatCompletionsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n async create(params: ChatCompletionCreateParams & { stream: true }): Promise<ChatCompletionStream>;\n async create(params: ChatCompletionCreateParams & { stream?: false | undefined }): Promise<ChatCompletionResponse>;\n async create(\n params: ChatCompletionCreateParams,\n ): Promise<ChatCompletionResponse | ChatCompletionStream> {\n const { body, request } = splitTransportOptions(params);\n\n if (params.stream) {\n const response = await this.http.postRaw('/llm/v1/chat/completions', body, request);\n if (!response.body) {\n throw new Error('Streaming response body is missing');\n }\n return new ChatCompletionStream(response.body);\n }\n\n return this.http.postJson<ChatCompletionResponse>('/llm/v1/chat/completions', body, request);\n }\n}\n","import type { EmbeddingsCreateParams, EmbeddingsResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\n\n/**\n * Pull SDK-level transport options out of the params so they don't ride\n * along inside the JSON body. Same shape as ChatCompletionsApi (DRY pattern).\n */\nfunction splitTransportOptions(params: EmbeddingsCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\nexport class EmbeddingsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Create one or more text embeddings. Wired to OpenRouter (default model\n * `nvidia/llama-nemotron-embed-vl-1b-v2:free`). Override via `model`.\n */\n create(params: EmbeddingsCreateParams): Promise<EmbeddingsResponse> {\n const { body, request } = splitTransportOptions(params);\n return this.http.postJson<EmbeddingsResponse>('/llm/v1/embeddings', body, request);\n }\n}\n","import type { ImageGenerationCreateParams, ImageGenerationResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\n\n/**\n * Pull SDK-level transport options out of the params so they don't ride\n * along inside the JSON body. Same shape as ChatCompletionsApi / EmbeddingsApi\n * (DRY pattern — every API class uses the same splitter).\n */\nfunction splitTransportOptions(params: ImageGenerationCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\n/**\n * `client.images.generate({ prompt, ... })` — OpenAI-compatible image generation\n * routed through the Builderforce gateway. The gateway cascades free Together\n * vendors → premium FluxAPI fallback so callers always see a successful\n * response unless every upstream is saturated. Read\n * `_builderforce.resolvedModel` / `resolvedVendor` to detect which vendor\n * served the request.\n *\n * Image generations are billed against the tenant's daily token budget at a\n * flat per-image rate (currently ~1000 tokens/image — deliberately conservative).\n * Hitting the cap returns the same `429 plan_token_limit_exceeded` envelope\n * as chat — caller code that already handles that path needs no changes.\n */\nexport class ImagesApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n generate(params: ImageGenerationCreateParams): Promise<ImageGenerationResponse> {\n const { body, request } = splitTransportOptions(params);\n return this.http.postJson<ImageGenerationResponse>('/llm/v1/images/generations', body, request);\n }\n}\n","import type { AiCapability, ModelInfo, ModelsListResponse } from '../domain/types';\nimport { HttpClient } from '../infrastructure/httpClient';\n\nexport class ModelsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */\n list(): Promise<ModelsListResponse> {\n return this.http.getJson<ModelsListResponse>('/llm/v1/models');\n }\n\n /**\n * Models in the tenant's plan pool, as structured entries. Empty when the\n * gateway is unconfigured for this tenant (no `data` branch — nothing servable).\n */\n async listInfo(): Promise<ModelInfo[]> {\n const res = await this.list();\n return res.data ?? [];\n }\n\n /**\n * Models whose `capabilities` include `capability`. By default only\n * currently-servable models are returned (`available: true`); pass\n * `{ includeUnavailable: true }` to include cooled / key-unbound ones too.\n */\n async listByCapability(\n capability: AiCapability,\n opts?: { includeUnavailable?: boolean },\n ): Promise<ModelInfo[]> {\n const includeUnavailable = opts?.includeUnavailable ?? false;\n const all = await this.listInfo();\n return all.filter(\n (m) =>\n (m.capabilities?.includes(capability) ?? false) &&\n (includeUnavailable || m.available),\n );\n }\n\n /**\n * Models that can read images and (page-rasterized) PDFs — i.e. those with the\n * `vision` OR `ocr` capability. This is the set a consumer that needs to ingest\n * images / documents (e.g. hired.video) should pick from.\n */\n async listImageCapable(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n const includeUnavailable = opts?.includeUnavailable ?? false;\n const all = await this.listInfo();\n return all.filter(\n (m) =>\n ((m.capabilities?.includes('vision') ?? false) ||\n (m.capabilities?.includes('ocr') ?? false)) &&\n (includeUnavailable || m.available),\n );\n }\n\n /** Models tuned for text extraction from images / documents (`ocr` capability). */\n listOcr(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n return this.listByCapability('ocr', opts);\n }\n\n /** Models that accept image content blocks (`vision` capability). */\n listVision(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n return this.listByCapability('vision', opts);\n }\n}\n","import type { UsageGetParams, UsageResponse } from '../domain/types';\nimport { HttpClient } from '../infrastructure/httpClient';\n\nexport class UsageApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n get(params: UsageGetParams = {}): Promise<UsageResponse> {\n const query = typeof params.days === 'number' ? `?days=${encodeURIComponent(String(params.days))}` : '';\n return this.http.getJson<UsageResponse>(`/llm/v1/usage${query}`);\n }\n}\n","import type { FailoverEvent } from '../domain/types';\n\nexport class BuilderforceApiError extends Error {\n public readonly status: number;\n public readonly code?: string;\n public readonly details?: unknown;\n public readonly requestId?: string;\n /**\n * `true` when the gateway has signalled this error will not resolve by\n * retrying on a different model — e.g. plan or per-claw daily token cap\n * exhausted (those caps are per-tenant, not per-model). Consumer-side\n * fallback chains should short-circuit when this is set.\n */\n public readonly terminal?: boolean;\n /** Seconds the consumer should wait before retrying — server-supplied. */\n public readonly retryAfter?: number;\n /**\n * Cascade attempts that failed before this error was returned — populated\n * when the gateway returns `429 cascade_exhausted` with a `details.failovers`\n * array. Each entry includes the vendor that owns the model so callers can\n * detect single-vendor saturation (e.g. all attempts on `openrouter`).\n */\n public readonly failovers?: FailoverEvent[];\n /**\n * Upstream vendor the gateway dispatched against (`'openrouter' | 'cerebras'\n * | 'nvidia' | 'ollama' | 'googleai' | …`). Set on every error where the\n * gateway selected an upstream — including single-attempt failures that\n * never ran a cascade (timeouts, single-vendor 429s, `model_unavailable`).\n *\n * Unset only for pre-dispatch errors where no vendor was ever selected:\n * `401`/`403` auth failures, `400` validation failures, `409` idempotent\n * replay, and tenant-cap 429s (`plan_token_limit_exceeded`,\n * `claw_token_limit_exceeded`) — those caps are per-tenant, not per-model.\n *\n * Sourced from the gateway's catalog lookup so consumers never have to\n * parse the model id to recover vendor identity.\n */\n public readonly vendor?: string;\n /**\n * Model id the gateway dispatched against — set whenever `vendor` is set.\n * Pair with `vendor` for per-attempt observability without prefix parsing.\n */\n public readonly model?: string;\n\n constructor(\n message: string,\n status: number,\n code?: string,\n details?: unknown,\n requestId?: string,\n extras?: { terminal?: boolean; retryAfter?: number; vendor?: string; model?: string },\n ) {\n super(message);\n this.name = 'BuilderforceApiError';\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\n this.terminal = extras?.terminal;\n this.retryAfter = extras?.retryAfter;\n this.vendor = extras?.vendor;\n this.model = extras?.model;\n // Pull typed failovers out of `details.failovers` when the gateway\n // supplied them. Validation is light — drop entries missing required\n // fields so consumers never get a partially-populated row.\n if (details && typeof details === 'object') {\n const f = (details as { failovers?: unknown }).failovers;\n if (Array.isArray(f)) {\n const cleaned: FailoverEvent[] = [];\n for (const entry of f) {\n if (entry && typeof entry === 'object') {\n const e = entry as { model?: unknown; vendor?: unknown; code?: unknown };\n if (typeof e.model === 'string' && typeof e.vendor === 'string' && typeof e.code === 'number') {\n const ev = entry as { durationMs?: unknown; kind?: unknown };\n cleaned.push({\n model: e.model, vendor: e.vendor, code: e.code,\n ...(typeof ev.durationMs === 'number' ? { durationMs: ev.durationMs } : {}),\n ...(typeof ev.kind === 'string' ? { kind: ev.kind } : {}),\n });\n }\n }\n }\n if (cleaned.length > 0) this.failovers = cleaned;\n }\n }\n }\n}\n\nexport interface HttpClientOptions {\n apiKey: string;\n baseUrl: string;\n fetchFn?: typeof fetch;\n /** Default per-request timeout in ms. Overridable per call. */\n timeoutMs?: number;\n}\n\n/** Per-request overrides — passed by the API layer, not by SDK consumers directly. */\nexport interface RequestOptions {\n /** Override the client default timeout for just this request. */\n timeoutMs?: number;\n /** Caller-provided AbortSignal. Linked together with the SDK's internal timeout\n * signal — whichever fires first aborts the request. */\n signal?: AbortSignal;\n /** Extra headers to merge in (e.g. `Idempotency-Key`). */\n headers?: Record<string, string>;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchFn: typeof fetch;\n private readonly defaultTimeoutMs: number;\n\n constructor(options: HttpClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n // Bind to `globalThis` so calling via `this.fetchFn(...)` doesn't trip\n // Cloudflare Workers' \"Illegal invocation\" check — the platform's fetch\n // requires the global receiver, not an instance method `this`. Affects\n // any environment that ships a strict-receiver fetch (Workers, Bun, etc.)\n // and is harmless on Node + browsers.\n const fetchImpl = options.fetchFn ?? fetch;\n this.fetchFn = fetchImpl.bind(globalThis);\n // 180s aligns the outer SDK budget with the premium routing path on the\n // gateway: per-vendor 60s × up to 3 PREMIUM-tier attempts = ~180s. Customers\n // running tailor / job-extract style long-context calls were hitting the\n // previous 60s cap before the gateway could finish its premium cascade.\n // Per-call `timeoutMs` still overrides for callers that want a tighter UX.\n this.defaultTimeoutMs = options.timeoutMs ?? 180_000;\n }\n\n async getJson<T>(path: string, options?: RequestOptions): Promise<T> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'GET',\n headers: this.mergeHeaders(options),\n }, options);\n return this.parseJsonResponse<T>(res);\n }\n\n async postJson<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.mergeHeaders(options, { 'Content-Type': 'application/json' }),\n body: JSON.stringify(body),\n }, options);\n return this.parseJsonResponse<T>(res);\n }\n\n async postRaw(path: string, body: unknown, options?: RequestOptions): Promise<Response> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.mergeHeaders(options, { 'Content-Type': 'application/json' }),\n body: JSON.stringify(body),\n }, options);\n if (!res.ok) {\n throw await this.toApiError(res);\n }\n return res;\n }\n\n private mergeHeaders(options?: RequestOptions, base?: Record<string, string>): Record<string, string> {\n return {\n Authorization: `Bearer ${this.apiKey}`,\n ...(base ?? {}),\n ...(options?.headers ?? {}),\n };\n }\n\n /**\n * Wrap a fetch in a combined abort signal: an internal timeout AND any\n * caller-provided signal. Either firing aborts the request. Single source of\n * abort plumbing — every method routes through here (DRY).\n */\n private async fetchWithTimeout(\n input: RequestInfo | URL,\n init: RequestInit,\n options?: RequestOptions,\n ): Promise<Response> {\n const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;\n const timeoutCtl = new AbortController();\n const timer = setTimeout(() => timeoutCtl.abort(), timeoutMs);\n\n // Combine internal timeout signal + caller signal. Native AbortSignal.any\n // (Node 20+ / modern Workers) is preferred; fall back to manual linking.\n const signal = combineSignals(timeoutCtl.signal, options?.signal);\n\n try {\n return await this.fetchFn(input, { ...init, signal });\n } catch (error) {\n if (timeoutCtl.signal.aborted) {\n throw new BuilderforceApiError(`Request timed out after ${timeoutMs}ms`, 408, 'timeout');\n }\n if (options?.signal?.aborted) {\n throw new BuilderforceApiError('Request aborted by caller', 499, 'aborted');\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseJsonResponse<T>(res: Response): Promise<T> {\n if (!res.ok) {\n throw await this.toApiError(res);\n }\n return res.json() as Promise<T>;\n }\n\n private async toApiError(res: Response): Promise<BuilderforceApiError> {\n const fallback = `Request failed (${res.status})`;\n const requestId = res.headers.get('x-request-id') ?? undefined;\n\n // Prefer server-supplied `Retry-After` header (seconds) when present; the\n // body's `retryAfter` is a fallback for environments that strip headers.\n const headerRetryAfter = parsePositiveInt(res.headers.get('retry-after'));\n\n try {\n const payload = await res.json() as Record<string, unknown> | null;\n\n // Three envelope shapes are in the wild:\n //\n // Flat — { error: \"msg\", code, details, terminal?, retryAfter? }\n // (gateway's documented shape — plan_token_limit_exceeded etc.)\n // OpenAI — { error: { message, code, type, details } }\n // (cascade-exhausted 429 on both chat and image surfaces —\n // matches OpenAI's error envelope convention)\n // Wrapped — { success: false, error: { code, message, details } }\n // (consumer-side wrappers around the gateway, e.g. some\n // tenant proxies emit AI_RATE_LIMITED / AI_UNAVAILABLE\n // envelopes that re-wrap the upstream error)\n //\n // Unwrap to a single `inner` shape so `details.failovers` etc. always\n // populate on `BuilderforceApiError` regardless of which envelope the\n // gateway picked. Single parsing site for every surface (DRY).\n const errorObj =\n payload !== null\n && typeof payload === 'object'\n && typeof payload.error === 'object'\n && payload.error !== null\n ? payload.error as Record<string, unknown>\n : null;\n\n const isWrapped =\n payload !== null\n && typeof payload === 'object'\n && payload.success === false\n && errorObj !== null;\n\n const inner = (isWrapped || errorObj !== null ? errorObj : payload) as {\n error?: string;\n message?: string;\n code?: string | number;\n details?: unknown;\n terminal?: boolean;\n retryAfter?: number;\n vendor?: string;\n model?: string;\n } | null;\n\n const message =\n (typeof inner?.message === 'string' && inner.message)\n || (typeof inner?.error === 'string' && inner.error)\n || fallback;\n\n // Coerce numeric error codes (e.g. cascade-exhausted emits `code: 429`)\n // to string so `BuilderforceApiError.code` stays a stable type for\n // consumer-side switches. String codes pass through unchanged.\n const code = typeof inner?.code === 'number' ? String(inner.code) : inner?.code;\n\n // Vendor/model may travel at the top of the error envelope (gateway\n // dispatched against an upstream) OR be embedded in details (e.g.\n // `model_unavailable` carries `details.requestedModel`). Prefer the\n // top-level fields; fall back to details so we still extract a model\n // from the strict-pin 503 envelope without a gateway change.\n const detailsObj = (inner?.details && typeof inner.details === 'object')\n ? inner.details as { vendor?: unknown; model?: unknown; requestedModel?: unknown }\n : null;\n const vendor = typeof inner?.vendor === 'string'\n ? inner.vendor\n : (typeof detailsObj?.vendor === 'string' ? detailsObj.vendor : undefined);\n const model = typeof inner?.model === 'string'\n ? inner.model\n : (typeof detailsObj?.model === 'string'\n ? detailsObj.model\n : (typeof detailsObj?.requestedModel === 'string' ? detailsObj.requestedModel : undefined));\n\n return new BuilderforceApiError(\n message,\n res.status,\n code,\n inner?.details,\n requestId,\n {\n terminal: inner?.terminal,\n retryAfter: headerRetryAfter ?? inner?.retryAfter,\n vendor,\n model,\n },\n );\n } catch {\n const text = await res.text().catch(() => '');\n return new BuilderforceApiError(\n text || fallback,\n res.status,\n undefined,\n undefined,\n requestId,\n headerRetryAfter !== undefined ? { retryAfter: headerRetryAfter } : undefined,\n );\n }\n }\n}\n\nfunction parsePositiveInt(s: string | null): number | undefined {\n if (s == null) return undefined;\n const n = Number(s);\n return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;\n}\n\n/**\n * Combine multiple AbortSignals into one. Uses native `AbortSignal.any` when\n * available (Node 20+, modern Workers); falls back to manual event linking.\n */\nfunction combineSignals(...signals: Array<AbortSignal | undefined>): AbortSignal {\n const live = signals.filter((s): s is AbortSignal => s !== undefined);\n if (live.length === 1) return live[0]!;\n\n const anyImpl = (AbortSignal as unknown as { any?: (signals: AbortSignal[]) => AbortSignal }).any;\n if (typeof anyImpl === 'function') {\n return anyImpl(live);\n }\n\n const ctl = new AbortController();\n for (const s of live) {\n if (s.aborted) { ctl.abort(s.reason); break; }\n s.addEventListener('abort', () => ctl.abort(s.reason), { once: true });\n }\n return ctl.signal;\n}\n","import { ChatCompletionsApi } from './application/ChatCompletionsApi';\nimport { EmbeddingsApi } from './application/EmbeddingsApi';\nimport { ImagesApi } from './application/ImagesApi';\nimport { ModelsApi } from './application/ModelsApi';\nimport { UsageApi } from './application/UsageApi';\nimport { BuilderforceApiError, HttpClient } from './infrastructure/httpClient';\n\nexport interface BuilderforceClientOptions {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n /** Default request timeout in ms (default 60_000). Per-call override available\n * via `chat.completions.create({ timeoutMs })` and `embeddings.create({ timeoutMs })`. */\n timeoutMs?: number;\n}\n\nexport class BuilderforceClient {\n public readonly chat: {\n completions: ChatCompletionsApi;\n };\n public readonly embeddings: EmbeddingsApi;\n public readonly images: ImagesApi;\n public readonly models: ModelsApi;\n public readonly usage: UsageApi;\n\n constructor(options: BuilderforceClientOptions) {\n const apiKey = options.apiKey?.trim();\n if (!apiKey) {\n throw new BuilderforceApiError(\n 'BuilderforceClient requires a non-empty apiKey',\n 400,\n 'missing_api_key',\n );\n }\n\n const http = new HttpClient({\n apiKey,\n baseUrl: options.baseUrl ?? 'https://api.builderforce.ai',\n fetchFn: options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n this.chat = {\n completions: new ChatCompletionsApi(http),\n };\n this.embeddings = new EmbeddingsApi(http);\n this.images = new ImagesApi(http);\n this.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n"],"mappings":"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","names":["splitTransportOptions","splitTransportOptions"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.ts","../src/application/ImagesApi.ts","../src/application/ModelsApi.ts","../src/application/UsageApi.ts","../src/infrastructure/httpClient.ts","../src/BuilderforceClient.ts","../src/application/classifyError.ts","../src/application/deriveResponseFormat.ts"],"sourcesContent":["export { BuilderforceClient, type BuilderforceClientOptions } from './BuilderforceClient';\n\nexport type {\n // Roles & content\n ChatRole,\n ChatMessage,\n ContentPart,\n TextContentPart,\n ImageUrlContentPart,\n // Tool calling\n ToolSpec,\n ToolCall,\n ToolCallFunction,\n ToolCallDelta,\n ToolChoice,\n FunctionDefinition,\n // Structured output\n ResponseFormat,\n JsonSchemaSpec,\n // Per-call options\n PerCallOptions,\n // Chat completions\n ChatCompletionCreateParams,\n ChatCompletionChunk,\n ChatCompletionResponse,\n FailoverEvent,\n FailoverKind,\n // Models / usage\n AiCapability,\n ModelInfo,\n ModelsListResponse,\n UsageByModel,\n UsageByDay,\n UsageByUser,\n UsageResponse,\n UsageGetParams,\n // Embeddings\n EmbeddingsCreateParams,\n EmbeddingsResponse,\n EmbeddingObject,\n // Image generation\n ImageGenerationCreateParams,\n ImageGenerationResponse,\n ImageGenerationDataEntry,\n} from './domain/types';\n\nexport { ChatCompletionStream } from './application/ChatCompletionsApi';\nexport { EmbeddingsApi } from './application/EmbeddingsApi';\nexport { ImagesApi } from './application/ImagesApi';\nexport { ModelsApi } from './application/ModelsApi';\nexport { BuilderforceApiError } from './infrastructure/httpClient';\n\n// Error classification — branch on the gateway's own failure taxonomy instead of\n// hand-rolling HTTP-status guesses (see classifyError).\nexport { classifyError } from './application/classifyError';\nexport type { ErrorKind, ErrorClassification } from './application/classifyError';\n\n// Response-format derivation — pick strict json_schema vs loose json_object by\n// schema complexity / vendor capability, pre-empting `schema_too_complex`.\nexport {\n deriveResponseFormat,\n canUseStrictSchema,\n estimateSchemaComplexity,\n DEFAULT_SCHEMA_COMPLEXITY_CEILING,\n} from './application/deriveResponseFormat';\nexport type { DeriveResponseFormatOptions, SchemaComplexity } from './application/deriveResponseFormat';\n","export async function* parseSseJson<T>(\n stream: ReadableStream<Uint8Array>,\n): AsyncGenerator<T, void, unknown> {\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data: ')) continue;\n\n const data = trimmed.slice(6).trim();\n if (data === '[DONE]') return;\n\n try {\n yield JSON.parse(data) as T;\n } catch {\n // Skip malformed chunks instead of breaking the stream.\n }\n }\n }\n}\n","import type { ChatCompletionChunk, ChatCompletionCreateParams, ChatCompletionResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\nimport { parseSseJson } from '../infrastructure/sse';\n\nexport class ChatCompletionStream implements AsyncIterable<ChatCompletionChunk> {\n private readonly stream: ReadableStream<Uint8Array>;\n\n constructor(stream: ReadableStream<Uint8Array>) {\n this.stream = stream;\n }\n\n [Symbol.asyncIterator](): AsyncIterator<ChatCompletionChunk, void, unknown> {\n return parseSseJson<ChatCompletionChunk>(this.stream);\n }\n\n async toText(): Promise<string> {\n let full = '';\n for await (const chunk of this) {\n const delta = chunk.choices?.[0]?.delta?.content;\n if (typeof delta === 'string') {\n full += delta;\n }\n }\n return full;\n }\n}\n\n/**\n * Pull SDK-level transport options (timeout, signal, idempotency key) out of\n * the params object so they don't get JSON-serialized into the request body.\n * Returns the request options AND the cleaned-up body.\n */\nfunction splitTransportOptions(params: ChatCompletionCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\nexport class ChatCompletionsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n async create(params: ChatCompletionCreateParams & { stream: true }): Promise<ChatCompletionStream>;\n async create(params: ChatCompletionCreateParams & { stream?: false | undefined }): Promise<ChatCompletionResponse>;\n async create(\n params: ChatCompletionCreateParams,\n ): Promise<ChatCompletionResponse | ChatCompletionStream> {\n const { body, request } = splitTransportOptions(params);\n\n if (params.stream) {\n const response = await this.http.postRaw('/llm/v1/chat/completions', body, request);\n if (!response.body) {\n throw new Error('Streaming response body is missing');\n }\n return new ChatCompletionStream(response.body);\n }\n\n return this.http.postJson<ChatCompletionResponse>('/llm/v1/chat/completions', body, request);\n }\n}\n","import type { EmbeddingsCreateParams, EmbeddingsResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\n\n/**\n * Pull SDK-level transport options out of the params so they don't ride\n * along inside the JSON body. Same shape as ChatCompletionsApi (DRY pattern).\n */\nfunction splitTransportOptions(params: EmbeddingsCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\nexport class EmbeddingsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /**\n * Create one or more text embeddings. Wired to OpenRouter (default model\n * `nvidia/llama-nemotron-embed-vl-1b-v2:free`). Override via `model`.\n */\n create(params: EmbeddingsCreateParams): Promise<EmbeddingsResponse> {\n const { body, request } = splitTransportOptions(params);\n return this.http.postJson<EmbeddingsResponse>('/llm/v1/embeddings', body, request);\n }\n}\n","import type { ImageGenerationCreateParams, ImageGenerationResponse } from '../domain/types';\nimport { HttpClient, type RequestOptions } from '../infrastructure/httpClient';\n\n/**\n * Pull SDK-level transport options out of the params so they don't ride\n * along inside the JSON body. Same shape as ChatCompletionsApi / EmbeddingsApi\n * (DRY pattern — every API class uses the same splitter).\n */\nfunction splitTransportOptions(params: ImageGenerationCreateParams): {\n body: Record<string, unknown>;\n request: RequestOptions;\n} {\n const { timeoutMs, signal, idempotencyKey, ...rest } = params;\n const headers: Record<string, string> = {};\n if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;\n return {\n body: rest as unknown as Record<string, unknown>,\n request: {\n timeoutMs,\n signal,\n ...(Object.keys(headers).length > 0 ? { headers } : {}),\n },\n };\n}\n\n/**\n * `client.images.generate({ prompt, ... })` — OpenAI-compatible image generation\n * routed through the Builderforce gateway. The gateway cascades free Together\n * vendors → premium FluxAPI fallback so callers always see a successful\n * response unless every upstream is saturated. Read\n * `_builderforce.resolvedModel` / `resolvedVendor` to detect which vendor\n * served the request.\n *\n * Image generations are billed against the tenant's daily token budget at a\n * flat per-image rate (currently ~1000 tokens/image — deliberately conservative).\n * Hitting the cap returns the same `429 plan_token_limit_exceeded` envelope\n * as chat — caller code that already handles that path needs no changes.\n */\nexport class ImagesApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n generate(params: ImageGenerationCreateParams): Promise<ImageGenerationResponse> {\n const { body, request } = splitTransportOptions(params);\n return this.http.postJson<ImageGenerationResponse>('/llm/v1/images/generations', body, request);\n }\n}\n","import type { AiCapability, ModelInfo, ModelsListResponse } from '../domain/types';\nimport { HttpClient } from '../infrastructure/httpClient';\n\nexport class ModelsApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n /** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */\n list(): Promise<ModelsListResponse> {\n return this.http.getJson<ModelsListResponse>('/llm/v1/models');\n }\n\n /**\n * Models in the tenant's plan pool, as structured entries. Empty when the\n * gateway is unconfigured for this tenant (no `data` branch — nothing servable).\n */\n async listInfo(): Promise<ModelInfo[]> {\n const res = await this.list();\n return res.data ?? [];\n }\n\n /**\n * Models whose `capabilities` include `capability`. By default only\n * currently-servable models are returned (`available: true`); pass\n * `{ includeUnavailable: true }` to include cooled / key-unbound ones too.\n */\n async listByCapability(\n capability: AiCapability,\n opts?: { includeUnavailable?: boolean },\n ): Promise<ModelInfo[]> {\n const includeUnavailable = opts?.includeUnavailable ?? false;\n const all = await this.listInfo();\n return all.filter(\n (m) =>\n (m.capabilities?.includes(capability) ?? false) &&\n (includeUnavailable || m.available),\n );\n }\n\n /**\n * Models that can read images and (page-rasterized) PDFs — i.e. those with the\n * `vision` OR `ocr` capability. This is the set a consumer that needs to ingest\n * images / documents (e.g. hired.video) should pick from.\n */\n async listImageCapable(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n const includeUnavailable = opts?.includeUnavailable ?? false;\n const all = await this.listInfo();\n return all.filter(\n (m) =>\n ((m.capabilities?.includes('vision') ?? false) ||\n (m.capabilities?.includes('ocr') ?? false)) &&\n (includeUnavailable || m.available),\n );\n }\n\n /** Models tuned for text extraction from images / documents (`ocr` capability). */\n listOcr(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n return this.listByCapability('ocr', opts);\n }\n\n /** Models that accept image content blocks (`vision` capability). */\n listVision(opts?: { includeUnavailable?: boolean }): Promise<ModelInfo[]> {\n return this.listByCapability('vision', opts);\n }\n}\n","import type { UsageGetParams, UsageResponse } from '../domain/types';\nimport { HttpClient } from '../infrastructure/httpClient';\n\nexport class UsageApi {\n private readonly http: HttpClient;\n\n constructor(http: HttpClient) {\n this.http = http;\n }\n\n get(params: UsageGetParams = {}): Promise<UsageResponse> {\n const query = typeof params.days === 'number' ? `?days=${encodeURIComponent(String(params.days))}` : '';\n return this.http.getJson<UsageResponse>(`/llm/v1/usage${query}`);\n }\n}\n","import type { FailoverEvent } from '../domain/types';\n\nexport class BuilderforceApiError extends Error {\n public readonly status: number;\n public readonly code?: string;\n public readonly details?: unknown;\n public readonly requestId?: string;\n /**\n * `true` when the gateway has signalled this error will not resolve by\n * retrying on a different model — e.g. plan or per-claw daily token cap\n * exhausted (those caps are per-tenant, not per-model). Consumer-side\n * fallback chains should short-circuit when this is set.\n */\n public readonly terminal?: boolean;\n /** Seconds the consumer should wait before retrying — server-supplied. */\n public readonly retryAfter?: number;\n /**\n * Cascade attempts that failed before this error was returned — populated\n * when the gateway returns `429 cascade_exhausted` with a `details.failovers`\n * array. Each entry includes the vendor that owns the model so callers can\n * detect single-vendor saturation (e.g. all attempts on `openrouter`).\n */\n public readonly failovers?: FailoverEvent[];\n /**\n * Upstream vendor the gateway dispatched against (`'openrouter' | 'cerebras'\n * | 'nvidia' | 'ollama' | 'googleai' | …`). Set on every error where the\n * gateway selected an upstream — including single-attempt failures that\n * never ran a cascade (timeouts, single-vendor 429s, `model_unavailable`).\n *\n * Unset only for pre-dispatch errors where no vendor was ever selected:\n * `401`/`403` auth failures, `400` validation failures, `409` idempotent\n * replay, and tenant-cap 429s (`plan_token_limit_exceeded`,\n * `claw_token_limit_exceeded`) — those caps are per-tenant, not per-model.\n *\n * Sourced from the gateway's catalog lookup so consumers never have to\n * parse the model id to recover vendor identity.\n */\n public readonly vendor?: string;\n /**\n * Model id the gateway dispatched against — set whenever `vendor` is set.\n * Pair with `vendor` for per-attempt observability without prefix parsing.\n */\n public readonly model?: string;\n\n constructor(\n message: string,\n status: number,\n code?: string,\n details?: unknown,\n requestId?: string,\n extras?: { terminal?: boolean; retryAfter?: number; vendor?: string; model?: string },\n ) {\n super(message);\n this.name = 'BuilderforceApiError';\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\n this.terminal = extras?.terminal;\n this.retryAfter = extras?.retryAfter;\n this.vendor = extras?.vendor;\n this.model = extras?.model;\n // Pull typed failovers out of `details.failovers` when the gateway\n // supplied them. Validation is light — drop entries missing required\n // fields so consumers never get a partially-populated row.\n if (details && typeof details === 'object') {\n const f = (details as { failovers?: unknown }).failovers;\n if (Array.isArray(f)) {\n const cleaned: FailoverEvent[] = [];\n for (const entry of f) {\n if (entry && typeof entry === 'object') {\n const e = entry as { model?: unknown; vendor?: unknown; code?: unknown };\n if (typeof e.model === 'string' && typeof e.vendor === 'string' && typeof e.code === 'number') {\n const ev = entry as { durationMs?: unknown; kind?: unknown; reason?: unknown; upstreamStatus?: unknown };\n cleaned.push({\n model: e.model, vendor: e.vendor, code: e.code,\n ...(typeof ev.durationMs === 'number' ? { durationMs: ev.durationMs } : {}),\n ...(typeof ev.kind === 'string' ? { kind: ev.kind } : {}),\n ...(typeof ev.reason === 'string' ? { reason: ev.reason } : {}),\n ...(typeof ev.upstreamStatus === 'number' ? { upstreamStatus: ev.upstreamStatus } : {}),\n });\n }\n }\n }\n if (cleaned.length > 0) this.failovers = cleaned;\n }\n }\n }\n}\n\nexport interface HttpClientOptions {\n apiKey: string;\n baseUrl: string;\n fetchFn?: typeof fetch;\n /** Default per-request timeout in ms. Overridable per call. */\n timeoutMs?: number;\n}\n\n/** Per-request overrides — passed by the API layer, not by SDK consumers directly. */\nexport interface RequestOptions {\n /** Override the client default timeout for just this request. */\n timeoutMs?: number;\n /** Caller-provided AbortSignal. Linked together with the SDK's internal timeout\n * signal — whichever fires first aborts the request. */\n signal?: AbortSignal;\n /** Extra headers to merge in (e.g. `Idempotency-Key`). */\n headers?: Record<string, string>;\n}\n\nexport class HttpClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchFn: typeof fetch;\n private readonly defaultTimeoutMs: number;\n\n constructor(options: HttpClientOptions) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl.replace(/\\/$/, '');\n // Bind to `globalThis` so calling via `this.fetchFn(...)` doesn't trip\n // Cloudflare Workers' \"Illegal invocation\" check — the platform's fetch\n // requires the global receiver, not an instance method `this`. Affects\n // any environment that ships a strict-receiver fetch (Workers, Bun, etc.)\n // and is harmless on Node + browsers.\n const fetchImpl = options.fetchFn ?? fetch;\n this.fetchFn = fetchImpl.bind(globalThis);\n // 180s aligns the outer SDK budget with the premium routing path on the\n // gateway: per-vendor 60s × up to 3 PREMIUM-tier attempts = ~180s. Customers\n // running tailor / job-extract style long-context calls were hitting the\n // previous 60s cap before the gateway could finish its premium cascade.\n // Per-call `timeoutMs` still overrides for callers that want a tighter UX.\n this.defaultTimeoutMs = options.timeoutMs ?? 180_000;\n }\n\n async getJson<T>(path: string, options?: RequestOptions): Promise<T> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'GET',\n headers: this.mergeHeaders(options),\n }, options);\n return this.parseJsonResponse<T>(res);\n }\n\n async postJson<T>(path: string, body: unknown, options?: RequestOptions): Promise<T> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.mergeHeaders(options, { 'Content-Type': 'application/json' }),\n body: JSON.stringify(body),\n }, options);\n return this.parseJsonResponse<T>(res);\n }\n\n async postRaw(path: string, body: unknown, options?: RequestOptions): Promise<Response> {\n const res = await this.fetchWithTimeout(`${this.baseUrl}${path}`, {\n method: 'POST',\n headers: this.mergeHeaders(options, { 'Content-Type': 'application/json' }),\n body: JSON.stringify(body),\n }, options);\n if (!res.ok) {\n throw await this.toApiError(res);\n }\n return res;\n }\n\n private mergeHeaders(options?: RequestOptions, base?: Record<string, string>): Record<string, string> {\n return {\n Authorization: `Bearer ${this.apiKey}`,\n ...(base ?? {}),\n ...(options?.headers ?? {}),\n };\n }\n\n /**\n * Wrap a fetch in a combined abort signal: an internal timeout AND any\n * caller-provided signal. Either firing aborts the request. Single source of\n * abort plumbing — every method routes through here (DRY).\n */\n private async fetchWithTimeout(\n input: RequestInfo | URL,\n init: RequestInit,\n options?: RequestOptions,\n ): Promise<Response> {\n const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;\n const timeoutCtl = new AbortController();\n const timer = setTimeout(() => timeoutCtl.abort(), timeoutMs);\n\n // Combine internal timeout signal + caller signal. Native AbortSignal.any\n // (Node 20+ / modern Workers) is preferred; fall back to manual linking.\n const signal = combineSignals(timeoutCtl.signal, options?.signal);\n\n try {\n return await this.fetchFn(input, { ...init, signal });\n } catch (error) {\n if (timeoutCtl.signal.aborted) {\n throw new BuilderforceApiError(`Request timed out after ${timeoutMs}ms`, 408, 'timeout');\n }\n if (options?.signal?.aborted) {\n throw new BuilderforceApiError('Request aborted by caller', 499, 'aborted');\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n private async parseJsonResponse<T>(res: Response): Promise<T> {\n if (!res.ok) {\n throw await this.toApiError(res);\n }\n return res.json() as Promise<T>;\n }\n\n private async toApiError(res: Response): Promise<BuilderforceApiError> {\n const fallback = `Request failed (${res.status})`;\n const requestId = res.headers.get('x-request-id') ?? undefined;\n\n // Prefer server-supplied `Retry-After` header (seconds) when present; the\n // body's `retryAfter` is a fallback for environments that strip headers.\n const headerRetryAfter = parsePositiveInt(res.headers.get('retry-after'));\n\n try {\n const payload = await res.json() as Record<string, unknown> | null;\n\n // Three envelope shapes are in the wild:\n //\n // Flat — { error: \"msg\", code, details, terminal?, retryAfter? }\n // (gateway's documented shape — plan_token_limit_exceeded etc.)\n // OpenAI — { error: { message, code, type, details } }\n // (cascade-exhausted 429 on both chat and image surfaces —\n // matches OpenAI's error envelope convention)\n // Wrapped — { success: false, error: { code, message, details } }\n // (consumer-side wrappers around the gateway, e.g. some\n // tenant proxies emit AI_RATE_LIMITED / AI_UNAVAILABLE\n // envelopes that re-wrap the upstream error)\n //\n // Unwrap to a single `inner` shape so `details.failovers` etc. always\n // populate on `BuilderforceApiError` regardless of which envelope the\n // gateway picked. Single parsing site for every surface (DRY).\n const errorObj =\n payload !== null\n && typeof payload === 'object'\n && typeof payload.error === 'object'\n && payload.error !== null\n ? payload.error as Record<string, unknown>\n : null;\n\n const isWrapped =\n payload !== null\n && typeof payload === 'object'\n && payload.success === false\n && errorObj !== null;\n\n const inner = (isWrapped || errorObj !== null ? errorObj : payload) as {\n error?: string;\n message?: string;\n code?: string | number;\n details?: unknown;\n terminal?: boolean;\n retryAfter?: number;\n vendor?: string;\n model?: string;\n } | null;\n\n const message =\n (typeof inner?.message === 'string' && inner.message)\n || (typeof inner?.error === 'string' && inner.error)\n || fallback;\n\n // Coerce numeric error codes (e.g. cascade-exhausted emits `code: 429`)\n // to string so `BuilderforceApiError.code` stays a stable type for\n // consumer-side switches. String codes pass through unchanged.\n const code = typeof inner?.code === 'number' ? String(inner.code) : inner?.code;\n\n // Vendor/model may travel at the top of the error envelope (gateway\n // dispatched against an upstream) OR be embedded in details (e.g.\n // `model_unavailable` carries `details.requestedModel`). Prefer the\n // top-level fields; fall back to details so we still extract a model\n // from the strict-pin 503 envelope without a gateway change.\n const detailsObj = (inner?.details && typeof inner.details === 'object')\n ? inner.details as { vendor?: unknown; model?: unknown; requestedModel?: unknown }\n : null;\n const vendor = typeof inner?.vendor === 'string'\n ? inner.vendor\n : (typeof detailsObj?.vendor === 'string' ? detailsObj.vendor : undefined);\n const model = typeof inner?.model === 'string'\n ? inner.model\n : (typeof detailsObj?.model === 'string'\n ? detailsObj.model\n : (typeof detailsObj?.requestedModel === 'string' ? detailsObj.requestedModel : undefined));\n\n return new BuilderforceApiError(\n message,\n res.status,\n code,\n inner?.details,\n requestId,\n {\n terminal: inner?.terminal,\n retryAfter: headerRetryAfter ?? inner?.retryAfter,\n vendor,\n model,\n },\n );\n } catch {\n const text = await res.text().catch(() => '');\n return new BuilderforceApiError(\n text || fallback,\n res.status,\n undefined,\n undefined,\n requestId,\n headerRetryAfter !== undefined ? { retryAfter: headerRetryAfter } : undefined,\n );\n }\n }\n}\n\nfunction parsePositiveInt(s: string | null): number | undefined {\n if (s == null) return undefined;\n const n = Number(s);\n return Number.isFinite(n) && n >= 0 ? Math.floor(n) : undefined;\n}\n\n/**\n * Combine multiple AbortSignals into one. Uses native `AbortSignal.any` when\n * available (Node 20+, modern Workers); falls back to manual event linking.\n */\nfunction combineSignals(...signals: Array<AbortSignal | undefined>): AbortSignal {\n const live = signals.filter((s): s is AbortSignal => s !== undefined);\n if (live.length === 1) return live[0]!;\n\n const anyImpl = (AbortSignal as unknown as { any?: (signals: AbortSignal[]) => AbortSignal }).any;\n if (typeof anyImpl === 'function') {\n return anyImpl(live);\n }\n\n const ctl = new AbortController();\n for (const s of live) {\n if (s.aborted) { ctl.abort(s.reason); break; }\n s.addEventListener('abort', () => ctl.abort(s.reason), { once: true });\n }\n return ctl.signal;\n}\n","import { ChatCompletionsApi } from './application/ChatCompletionsApi';\nimport { EmbeddingsApi } from './application/EmbeddingsApi';\nimport { ImagesApi } from './application/ImagesApi';\nimport { ModelsApi } from './application/ModelsApi';\nimport { UsageApi } from './application/UsageApi';\nimport { BuilderforceApiError, HttpClient } from './infrastructure/httpClient';\n\nexport interface BuilderforceClientOptions {\n apiKey: string;\n baseUrl?: string;\n fetch?: typeof fetch;\n /** Default request timeout in ms (default 60_000). Per-call override available\n * via `chat.completions.create({ timeoutMs })` and `embeddings.create({ timeoutMs })`. */\n timeoutMs?: number;\n}\n\nexport class BuilderforceClient {\n public readonly chat: {\n completions: ChatCompletionsApi;\n };\n public readonly embeddings: EmbeddingsApi;\n public readonly images: ImagesApi;\n public readonly models: ModelsApi;\n public readonly usage: UsageApi;\n\n constructor(options: BuilderforceClientOptions) {\n const apiKey = options.apiKey?.trim();\n if (!apiKey) {\n throw new BuilderforceApiError(\n 'BuilderforceClient requires a non-empty apiKey',\n 400,\n 'missing_api_key',\n );\n }\n\n const http = new HttpClient({\n apiKey,\n baseUrl: options.baseUrl ?? 'https://api.builderforce.ai',\n fetchFn: options.fetch,\n timeoutMs: options.timeoutMs,\n });\n\n this.chat = {\n completions: new ChatCompletionsApi(http),\n };\n this.embeddings = new EmbeddingsApi(http);\n this.images = new ImagesApi(http);\n this.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n","import { BuilderforceApiError } from '../infrastructure/httpClient';\n\n/**\n * Coarse, stable error class for a failed gateway call — keyed off the gateway's\n * OWN failure taxonomy (`error.code` + `terminal` + the failover breakdown), NOT\n * raw HTTP-status guessing. Branch on this instead of reinventing a classifier\n * per consumer (which inevitably drifts).\n *\n * rate_limit — the gateway's whole cascade was rate-limited (429\n * `cascade_exhausted`). Retry later (`retryAfter`).\n * token_cap — a per-TENANT cap was hit (plan/monthly/host/claw token\n * or image-credit limit). TERMINAL for this billing\n * window — a different model won't help.\n * schema_too_complex — every candidate rejected the `response_format.json_schema`\n * as too complex for its constrained-decoding engine.\n * TERMINAL: simplify the schema or drop to `json_object`.\n * invalid_request — malformed payload (400/422) every model rejected. TERMINAL.\n * auth — bad/missing API key (401/403). TERMINAL.\n * model_unavailable — a strict-pinned model is on cooldown / unconfigured (503).\n * Not terminal: drop the pin or pick another model.\n * timeout — the request (or a single vendor attempt) timed out (408).\n * service_unavailable — infrastructure ceiling (503 `worker_subrequest_exhausted`)\n * or transient upstream outage (5xx). Retry after a backoff.\n * content_filter — a safety system blocked the generation.\n * network — the request never reached the gateway (DNS/TLS/reset).\n * aborted — the caller's AbortSignal fired (499 / AbortError).\n * unknown — none of the above matched.\n */\nexport type ErrorKind =\n | 'rate_limit'\n | 'token_cap'\n | 'schema_too_complex'\n | 'invalid_request'\n | 'auth'\n | 'model_unavailable'\n | 'timeout'\n | 'service_unavailable'\n | 'content_filter'\n | 'network'\n | 'aborted'\n | 'unknown';\n\nexport interface ErrorClassification {\n kind: ErrorKind;\n /**\n * `true` when retrying the SAME request on a DIFFERENT model will NOT help —\n * the consumer's own failover chain should short-circuit. Sourced from the\n * gateway's `error.terminal` flag when present, with a kind-based fallback.\n */\n terminal: boolean;\n /**\n * `true` when the SAME request is safe to retry as-is (idempotently), usually\n * after `retryAfter` seconds — e.g. a transient rate-limit/outage/timeout.\n * `false` for deterministic rejections (schema, invalid request, auth, caps).\n */\n retryable: boolean;\n /** Seconds the caller should wait before retrying, when the gateway supplied it. */\n retryAfter?: number;\n /** HTTP status, when the error reached the gateway. */\n status?: number;\n /** Gateway error code slug, when present (`schema_too_complex`, `plan_token_limit_exceeded`, …). */\n code?: string;\n /** Human-readable message (the gateway's, or the thrown error's). */\n message: string;\n}\n\n/** Tenant-cap codes — all per-tenant (not per-model), so all TERMINAL for the window. */\nconst TOKEN_CAP_CODES: ReadonlySet<string> = new Set([\n 'plan_token_limit_exceeded',\n 'plan_monthly_token_limit_exceeded',\n 'agent_host_token_limit_exceeded',\n 'claw_token_limit_exceeded',\n 'image_credit_limit_exceeded',\n]);\n\n/**\n * Classify any caught error from a Builderforce SDK call into a structured,\n * actionable verdict. Accepts `unknown` so a consumer can pass a raw `catch`\n * binding — non-`BuilderforceApiError` values (network throws, `AbortError`,\n * plain `Error`) are classified too.\n *\n * This is the FIRST-PARTY classifier the gateway feedback asked for: keyed off\n * the gateway's own taxonomy so every consumer agrees on what \"terminal\" and\n * \"retryable\" mean instead of hand-rolling `429/408/401/5xx → kind` guesses that\n * drift apart.\n */\nexport function classifyError(err: unknown): ErrorClassification {\n // ── Non-gateway throws ────────────────────────────────────────────────────\n if (!(err instanceof BuilderforceApiError)) {\n const name = (err as { name?: unknown } | null)?.name;\n const message = err instanceof Error ? err.message : String(err);\n if (name === 'AbortError') {\n return { kind: 'aborted', terminal: true, retryable: false, message };\n }\n // A TypeError from fetch (failed to fetch / network) never reached the gateway.\n if (err instanceof TypeError) {\n return { kind: 'network', terminal: false, retryable: true, message };\n }\n return { kind: 'unknown', terminal: false, retryable: false, message };\n }\n\n const { status, code, terminal, retryAfter, message } = err;\n const base = {\n ...(retryAfter !== undefined ? { retryAfter } : {}),\n ...(status !== undefined ? { status } : {}),\n ...(code !== undefined ? { code } : {}),\n message,\n };\n\n // ── Code-slug routing (authoritative — the gateway's own taxonomy) ─────────\n if (code === 'schema_too_complex' || lastFailoverReason(err) === 'schema_too_complex') {\n return { kind: 'schema_too_complex', terminal: terminal ?? true, retryable: false, ...base };\n }\n if (code && TOKEN_CAP_CODES.has(code)) {\n return { kind: 'token_cap', terminal: terminal ?? true, retryable: false, ...base };\n }\n if (code === 'model_unavailable') {\n return { kind: 'model_unavailable', terminal: terminal ?? false, retryable: false, ...base };\n }\n if (code === 'worker_subrequest_exhausted') {\n return { kind: 'service_unavailable', terminal: false, retryable: true, ...base };\n }\n if (code === 'aborted') {\n return { kind: 'aborted', terminal: true, retryable: false, ...base };\n }\n if (code === 'content_filter') {\n return { kind: 'content_filter', terminal: terminal ?? true, retryable: false, ...base };\n }\n\n // ── Status routing (fallback when no specific code applies) ────────────────\n if (status === 408 || code === 'timeout') {\n return { kind: 'timeout', terminal: false, retryable: true, ...base };\n }\n if (status === 401 || status === 403) {\n return { kind: 'auth', terminal: terminal ?? true, retryable: false, ...base };\n }\n if (status === 429) {\n return { kind: 'rate_limit', terminal: terminal ?? false, retryable: !(terminal ?? false), ...base };\n }\n if (status === 400 || status === 422) {\n return { kind: 'invalid_request', terminal: terminal ?? true, retryable: false, ...base };\n }\n if (status === 503) {\n return { kind: 'service_unavailable', terminal: false, retryable: true, ...base };\n }\n if (status !== undefined && status >= 500) {\n return { kind: 'service_unavailable', terminal: false, retryable: true, ...base };\n }\n\n return { kind: 'unknown', terminal: terminal ?? false, retryable: false, ...base };\n}\n\n/** The `reason` slug of the last cascade attempt, if any — lets `classifyError`\n * recognise a schema rejection on an older gateway that set the failover `reason`\n * but not the top-level `code`. */\nfunction lastFailoverReason(err: BuilderforceApiError): string | undefined {\n const f = err.failovers;\n if (!f || f.length === 0) return undefined;\n return f[f.length - 1]?.reason;\n}\n","import type { ResponseFormat } from '../domain/types';\n\n/**\n * deriveResponseFormat — pick the strongest `response_format` a request can SAFELY\n * use given how complex its JSON-Schema is and (optionally) which vendor will\n * serve it.\n *\n * The problem this solves: a strict `json_schema` gives the best conformance, but\n * some vendors' constrained-decoding engines reject a schema that's too complex\n * (Gemini's \"too many states for serving\"). The gateway now surfaces that as a\n * terminal `schema_too_complex` error — but the cleaner fix is to NOT send a\n * strict schema a vendor can't honour in the first place. This utility is the\n * pre-flight guard: it emits `{ type: 'json_schema', strict }` when the schema is\n * within the (vendor-specific or conservative-default) complexity ceiling, and\n * falls back to `{ type: 'json_object' }` (loose JSON mode — universally\n * supported) when it isn't.\n *\n * The SDK is zero-dependency, so this takes a plain JSON-Schema object — convert\n * a Zod schema first with `zod-to-json-schema` (`deriveResponseFormat(zodToJsonSchema(MySchema), …)`).\n */\n\nexport interface DeriveResponseFormatOptions {\n /** Schema name sent as `json_schema.name` (default `'response'`). */\n name?: string;\n /** Set `json_schema.strict` when a strict schema is emitted (default `true`). */\n strict?: boolean;\n /**\n * The vendor that will serve the request, when known (the consumer pinned a\n * `model`). Selects that vendor's specific complexity ceiling. Omit when\n * routing is gateway-owned — the conservative default ceiling (the lowest\n * common denominator across vendors) is used so the schema is accepted\n * whichever vendor the gateway picks.\n */\n vendor?: string;\n /**\n * Override the complexity ceiling (max schema \"nodes\"; see\n * {@link estimateSchemaComplexity}). Above this, loose `json_object` is emitted.\n * Wins over the vendor/default ceiling.\n */\n maxComplexity?: number;\n}\n\nexport interface SchemaComplexity {\n /** Total schema nodes — every property, array `items`, and enum value counts one. */\n nodes: number;\n /** Deepest nesting level reached. */\n maxDepth: number;\n /** Total enum values across the whole schema (the main driver of constrained-\n * decoding state blow-up). */\n totalEnumValues: number;\n /** Single rolled-up score compared against the ceiling: `nodes + totalEnumValues`. */\n score: number;\n}\n\n/**\n * Conservative cross-vendor ceiling, used when no `vendor` is supplied (gateway-\n * owned routing). Tuned below the lowest-ceiling vendor (Gemini's constrained-\n * decoding \"too many states\" limit) so a schema that passes here is accepted by\n * ANY vendor the gateway might route to.\n */\nexport const DEFAULT_SCHEMA_COMPLEXITY_CEILING = 80;\n\n/**\n * Per-vendor strict-`json_schema` complexity ceilings (heuristic). A vendor absent\n * from this map is assumed capable at the {@link DEFAULT_SCHEMA_COMPLEXITY_CEILING}.\n * `0` marks a vendor that does NOT support strict json_schema at all (always loose).\n * These are SDK-side heuristics — the authoritative limits ride the gateway model\n * catalog; tune via `maxComplexity` when you have vendor-specific knowledge.\n */\nconst VENDOR_SCHEMA_CEILINGS: Readonly<Record<string, number>> = {\n // Low constrained-decoding ceiling — the vendor that motivated this guard.\n googleai: 60,\n google: 60,\n gemini: 60,\n // High-ceiling, robust strict-schema vendors.\n openai: 600,\n anthropic: 600,\n cerebras: 300,\n nvidia: 300,\n openrouter: 200,\n};\n\nconst MAX_SCHEMA_WALK_DEPTH = 64; // cycle / runaway-recursion guard\n\n/**\n * Estimate a JSON-Schema's complexity. The dominant cost for constrained decoding\n * is the number of distinct states the engine must track, which grows with the\n * node count and (especially) the total number of enum values. Pure + cheap.\n */\nexport function estimateSchemaComplexity(schema: unknown): SchemaComplexity {\n let nodes = 0;\n let totalEnumValues = 0;\n let maxDepth = 0;\n\n const walk = (node: unknown, depth: number): void => {\n if (depth > MAX_SCHEMA_WALK_DEPTH || node === null || typeof node !== 'object') return;\n if (depth > maxDepth) maxDepth = depth;\n const s = node as Record<string, unknown>;\n\n const enumVals = s['enum'];\n if (Array.isArray(enumVals)) totalEnumValues += enumVals.length;\n\n const props = s['properties'];\n if (props && typeof props === 'object') {\n for (const key of Object.keys(props)) {\n nodes += 1;\n walk((props as Record<string, unknown>)[key], depth + 1);\n }\n }\n\n // Array item schemas (single schema or tuple form).\n const items = s['items'];\n if (Array.isArray(items)) items.forEach((it) => { nodes += 1; walk(it, depth + 1); });\n else if (items && typeof items === 'object') { nodes += 1; walk(items, depth + 1); }\n\n // Composition keywords contribute their branches.\n for (const comb of ['anyOf', 'oneOf', 'allOf'] as const) {\n const arr = s[comb];\n if (Array.isArray(arr)) arr.forEach((sub) => { nodes += 1; walk(sub, depth + 1); });\n }\n\n // `$defs` / `definitions` referenced shapes.\n for (const defsKey of ['$defs', 'definitions'] as const) {\n const defs = s[defsKey];\n if (defs && typeof defs === 'object') {\n for (const key of Object.keys(defs)) walk((defs as Record<string, unknown>)[key], depth + 1);\n }\n }\n };\n\n walk(schema, 0);\n return { nodes, maxDepth, totalEnumValues, score: nodes + totalEnumValues };\n}\n\n/** The complexity ceiling that applies for the given options. */\nfunction ceilingFor(opts?: DeriveResponseFormatOptions): number {\n if (opts?.maxComplexity != null && opts.maxComplexity >= 0) return opts.maxComplexity;\n if (opts?.vendor) {\n const v = opts.vendor.toLowerCase();\n if (v in VENDOR_SCHEMA_CEILINGS) return VENDOR_SCHEMA_CEILINGS[v]!;\n }\n return DEFAULT_SCHEMA_COMPLEXITY_CEILING;\n}\n\n/**\n * True when a strict `json_schema` is safe for the given schema + vendor (i.e.\n * within the complexity ceiling, and the vendor isn't strict-schema-incapable).\n * Exposed so callers can branch (e.g. log a downgrade) without re-deriving.\n */\nexport function canUseStrictSchema(schema: unknown, opts?: DeriveResponseFormatOptions): boolean {\n const ceiling = ceilingFor(opts);\n if (ceiling === 0) return false; // vendor declared incapable\n return estimateSchemaComplexity(schema).score <= ceiling;\n}\n\n/**\n * Derive the strongest safe `response_format`:\n * • within the ceiling → `{ type: 'json_schema', json_schema: { name, schema, strict } }`\n * • over the ceiling → `{ type: 'json_object' }` (loose JSON; instruct the\n * model to follow the shape in your prompt)\n *\n * Pure — returns a value the consumer drops straight into\n * `chat.completions.create({ response_format })`.\n */\nexport function deriveResponseFormat(\n schema: Record<string, unknown>,\n opts?: DeriveResponseFormatOptions,\n): ResponseFormat {\n if (!canUseStrictSchema(schema, opts)) {\n return { type: 'json_object' };\n }\n return {\n type: 'json_schema',\n json_schema: {\n name: opts?.name ?? 'response',\n schema,\n strict: opts?.strict ?? true,\n },\n };\n}\n"],"mappings":"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","names":["splitTransportOptions","splitTransportOptions","message"]}