@seanhogg/builderforce-sdk 2026.6.29 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../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 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/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 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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanhogg/builderforce-sdk",
3
- "version": "2026.6.29",
3
+ "version": "2026.6.30",
4
4
  "description": "Typed SDK for the Builderforce.ai LLM gateway — chat completions with tool-calling and structured output, embeddings, image generation, models, and usage analytics over an OpenAI-compatible surface.",
5
5
  "license": "MIT",
6
6
  "author": "Sean Hogg",