@seanhogg/builderforce-sdk 0.8.2 → 2026.6.29
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 +30 -0
- package/dist/index.cjs +45 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -8
- package/dist/index.d.ts +66 -8
- package/dist/index.mjs +43 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -425,6 +425,36 @@ const usage = await client.usage.get({ days: 30 });
|
|
|
425
425
|
|
|
426
426
|
`usage` returns aggregate spend by model, day, and user; plus `mine` (the calling user's slice) and `totals`. Pass `?detail=true&page=1&limit=100` for row-level pagination — every recorded call with its `useCase`, `metadata`, `idempotencyKey`, and token counts. Use this to reconcile your own usage table against the gateway's ledger.
|
|
427
427
|
|
|
428
|
+
### Capability discovery — which models read images / PDFs
|
|
429
|
+
|
|
430
|
+
Each entry in `models.list().data` carries a `capabilities` array — `vision` (accepts `image_url` content blocks; reads images and page-rasterized PDFs), `ocr` (tuned for text extraction), `tools` (tool-calling), and `structured_output` (`json_schema`). Convenience helpers filter the pool so you never hard-code model ids:
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
// Models that can read images and PDFs (vision OR ocr) — the set to pick from
|
|
434
|
+
// when a user uploads a screenshot, scan, or document.
|
|
435
|
+
const imageModels = await client.models.listImageCapable();
|
|
436
|
+
// → [{ model: 'google/gemini-2.5-pro', capabilities: ['tools','structured_output','vision'], available: true, ... }, ...]
|
|
437
|
+
|
|
438
|
+
const ocrModels = await client.models.listOcr(); // ocr capability only
|
|
439
|
+
const visionModels = await client.models.listVision(); // vision capability only
|
|
440
|
+
const toolModels = await client.models.listByCapability('tools');
|
|
441
|
+
|
|
442
|
+
// Send an image to the first available image-capable model:
|
|
443
|
+
const res = await client.chat.completions.create({
|
|
444
|
+
model: imageModels[0]?.model,
|
|
445
|
+
messages: [{
|
|
446
|
+
role: 'user',
|
|
447
|
+
content: [
|
|
448
|
+
{ type: 'text', text: 'Extract the invoice total from this image.' },
|
|
449
|
+
{ type: 'image_url', image_url: { url: 'data:image/png;base64,...' } },
|
|
450
|
+
],
|
|
451
|
+
}],
|
|
452
|
+
useCase: 'invoice_ocr', // a useCase containing "ocr" also nudges the gateway's own routing toward OCR models
|
|
453
|
+
});
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
All capability helpers return only currently-servable models by default (`available: true`); pass `{ includeUnavailable: true }` to include cooled / key-unbound ones. They return `[]` when the gateway is unconfigured for the tenant (nothing servable). You don't strictly need to pre-select — when `model` is unset, the gateway already promotes vision/ocr models to the front of the chain for requests that carry image content (or an `ocr` `useCase`) — but listing them lets you show the user a picker or pin a specific one.
|
|
457
|
+
|
|
428
458
|
## Routing — `model` is a hint, gateway has final say
|
|
429
459
|
|
|
430
460
|
The gateway owns model selection. When you pass a `model`, the gateway treats it as a **hint** — it puts that id at the head of its candidate chain so it's tried first, but it retains the right to substitute on cooldown, vendor outage, or plan-tier mismatch. **Always read `_builderforce.resolvedModel` if you need to know what actually ran.** Pair it with `_builderforce.resolvedVendor` (the upstream that owns the resolved model — `'openrouter'`, `'cerebras'`, `'nvidia'`, `'ollama'`, `'googleai'`, …) for per-vendor cost / latency aggregation without parsing the model-id prefix.
|
package/dist/index.cjs
CHANGED
|
@@ -24,7 +24,8 @@ __export(index_exports, {
|
|
|
24
24
|
BuilderforceClient: () => BuilderforceClient,
|
|
25
25
|
ChatCompletionStream: () => ChatCompletionStream,
|
|
26
26
|
EmbeddingsApi: () => EmbeddingsApi,
|
|
27
|
-
ImagesApi: () => ImagesApi
|
|
27
|
+
ImagesApi: () => ImagesApi,
|
|
28
|
+
ModelsApi: () => ModelsApi
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
30
31
|
|
|
@@ -163,9 +164,50 @@ var ModelsApi = class {
|
|
|
163
164
|
constructor(http) {
|
|
164
165
|
this.http = http;
|
|
165
166
|
}
|
|
167
|
+
/** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */
|
|
166
168
|
list() {
|
|
167
169
|
return this.http.getJson("/llm/v1/models");
|
|
168
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Models in the tenant's plan pool, as structured entries. Empty when the
|
|
173
|
+
* gateway is unconfigured for this tenant (no `data` branch — nothing servable).
|
|
174
|
+
*/
|
|
175
|
+
async listInfo() {
|
|
176
|
+
const res = await this.list();
|
|
177
|
+
return res.data ?? [];
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Models whose `capabilities` include `capability`. By default only
|
|
181
|
+
* currently-servable models are returned (`available: true`); pass
|
|
182
|
+
* `{ includeUnavailable: true }` to include cooled / key-unbound ones too.
|
|
183
|
+
*/
|
|
184
|
+
async listByCapability(capability, opts) {
|
|
185
|
+
const includeUnavailable = opts?.includeUnavailable ?? false;
|
|
186
|
+
const all = await this.listInfo();
|
|
187
|
+
return all.filter(
|
|
188
|
+
(m) => (m.capabilities?.includes(capability) ?? false) && (includeUnavailable || m.available)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Models that can read images and (page-rasterized) PDFs — i.e. those with the
|
|
193
|
+
* `vision` OR `ocr` capability. This is the set a consumer that needs to ingest
|
|
194
|
+
* images / documents (e.g. hired.video) should pick from.
|
|
195
|
+
*/
|
|
196
|
+
async listImageCapable(opts) {
|
|
197
|
+
const includeUnavailable = opts?.includeUnavailable ?? false;
|
|
198
|
+
const all = await this.listInfo();
|
|
199
|
+
return all.filter(
|
|
200
|
+
(m) => ((m.capabilities?.includes("vision") ?? false) || (m.capabilities?.includes("ocr") ?? false)) && (includeUnavailable || m.available)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
/** Models tuned for text extraction from images / documents (`ocr` capability). */
|
|
204
|
+
listOcr(opts) {
|
|
205
|
+
return this.listByCapability("ocr", opts);
|
|
206
|
+
}
|
|
207
|
+
/** Models that accept image content blocks (`vision` capability). */
|
|
208
|
+
listVision(opts) {
|
|
209
|
+
return this.listByCapability("vision", opts);
|
|
210
|
+
}
|
|
169
211
|
};
|
|
170
212
|
|
|
171
213
|
// src/application/UsageApi.ts
|
|
@@ -432,6 +474,7 @@ var BuilderforceClient = class {
|
|
|
432
474
|
BuilderforceClient,
|
|
433
475
|
ChatCompletionStream,
|
|
434
476
|
EmbeddingsApi,
|
|
435
|
-
ImagesApi
|
|
477
|
+
ImagesApi,
|
|
478
|
+
ModelsApi
|
|
436
479
|
});
|
|
437
480
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 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 { 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 { 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 list(): Promise<ModelsListResponse> {\n return this.http.getJson<ModelsListResponse>('/llm/v1/models');\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"],"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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -256,18 +256,46 @@ interface ChatCompletionResponse {
|
|
|
256
256
|
};
|
|
257
257
|
[key: string]: unknown;
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Shape capability a model supports — what kinds of request it can serve.
|
|
261
|
+
* `vision` — accepts image content blocks (`image_url`); reads images and
|
|
262
|
+
* page-rasterized PDFs.
|
|
263
|
+
* `ocr` — tuned for text extraction from images / documents.
|
|
264
|
+
* `tools` — honours `tools` / `tool_choice` round-trips.
|
|
265
|
+
* `structured_output` — reliably emits valid JSON / honours `json_schema`.
|
|
266
|
+
*
|
|
267
|
+
* Consumers that need to read images or PDFs (e.g. hired.video) should pick a
|
|
268
|
+
* model whose `capabilities` include `vision` or `ocr`. See
|
|
269
|
+
* `models.listImageCapable()` / `models.listOcr()`.
|
|
270
|
+
*/
|
|
271
|
+
type AiCapability = 'tools' | 'structured_output' | 'vision' | 'ocr';
|
|
272
|
+
/** One model in the tenant-plan pool, with availability + capability metadata. */
|
|
273
|
+
interface ModelInfo {
|
|
274
|
+
model: string;
|
|
275
|
+
vendor: string;
|
|
276
|
+
/** In the top "preferred" sub-pool the gateway round-robins across first. */
|
|
277
|
+
preferred: boolean;
|
|
278
|
+
/** Servable right now — key bound and not on per-model / per-vendor cooldown. */
|
|
279
|
+
available: boolean;
|
|
280
|
+
/** Epoch ms when the per-model cooldown lifts. Absent when not cooling. */
|
|
281
|
+
cooldownUntil?: number;
|
|
282
|
+
/** Epoch ms when the per-vendor cooldown lifts. Set when an upstream is wholesale-cooled. */
|
|
283
|
+
vendorCooledUntil?: number;
|
|
284
|
+
/** Whether the vendor's API key is bound. False → model is unservable. */
|
|
285
|
+
keyBound?: boolean;
|
|
286
|
+
/** Shape capabilities this model supports — drives image/PDF (`vision`/`ocr`),
|
|
287
|
+
* tool-calling (`tools`), and structured-output (`structured_output`) routing.
|
|
288
|
+
* Absent on older gateways that don't yet emit it. */
|
|
289
|
+
capabilities?: AiCapability[];
|
|
290
|
+
}
|
|
259
291
|
interface ModelsListResponse {
|
|
260
292
|
configured?: boolean;
|
|
261
293
|
object?: 'list';
|
|
262
294
|
product?: string;
|
|
263
295
|
effectivePlan?: string;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
preferred: boolean;
|
|
268
|
-
available: boolean;
|
|
269
|
-
cooldownUntil?: number;
|
|
270
|
-
}>;
|
|
296
|
+
/** Per-model pool status (present on the `configured: true` branch). */
|
|
297
|
+
data?: ModelInfo[];
|
|
298
|
+
/** Bare model-id pool (present on the `configured: false` branch). */
|
|
271
299
|
models?: string[];
|
|
272
300
|
[key: string]: unknown;
|
|
273
301
|
}
|
|
@@ -544,7 +572,37 @@ declare class ImagesApi {
|
|
|
544
572
|
declare class ModelsApi {
|
|
545
573
|
private readonly http;
|
|
546
574
|
constructor(http: HttpClient);
|
|
575
|
+
/** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */
|
|
547
576
|
list(): Promise<ModelsListResponse>;
|
|
577
|
+
/**
|
|
578
|
+
* Models in the tenant's plan pool, as structured entries. Empty when the
|
|
579
|
+
* gateway is unconfigured for this tenant (no `data` branch — nothing servable).
|
|
580
|
+
*/
|
|
581
|
+
listInfo(): Promise<ModelInfo[]>;
|
|
582
|
+
/**
|
|
583
|
+
* Models whose `capabilities` include `capability`. By default only
|
|
584
|
+
* currently-servable models are returned (`available: true`); pass
|
|
585
|
+
* `{ includeUnavailable: true }` to include cooled / key-unbound ones too.
|
|
586
|
+
*/
|
|
587
|
+
listByCapability(capability: AiCapability, opts?: {
|
|
588
|
+
includeUnavailable?: boolean;
|
|
589
|
+
}): Promise<ModelInfo[]>;
|
|
590
|
+
/**
|
|
591
|
+
* Models that can read images and (page-rasterized) PDFs — i.e. those with the
|
|
592
|
+
* `vision` OR `ocr` capability. This is the set a consumer that needs to ingest
|
|
593
|
+
* images / documents (e.g. hired.video) should pick from.
|
|
594
|
+
*/
|
|
595
|
+
listImageCapable(opts?: {
|
|
596
|
+
includeUnavailable?: boolean;
|
|
597
|
+
}): Promise<ModelInfo[]>;
|
|
598
|
+
/** Models tuned for text extraction from images / documents (`ocr` capability). */
|
|
599
|
+
listOcr(opts?: {
|
|
600
|
+
includeUnavailable?: boolean;
|
|
601
|
+
}): Promise<ModelInfo[]>;
|
|
602
|
+
/** Models that accept image content blocks (`vision` capability). */
|
|
603
|
+
listVision(opts?: {
|
|
604
|
+
includeUnavailable?: boolean;
|
|
605
|
+
}): Promise<ModelInfo[]>;
|
|
548
606
|
}
|
|
549
607
|
|
|
550
608
|
declare class UsageApi {
|
|
@@ -572,4 +630,4 @@ declare class BuilderforceClient {
|
|
|
572
630
|
constructor(options: BuilderforceClientOptions);
|
|
573
631
|
}
|
|
574
632
|
|
|
575
|
-
export { BuilderforceApiError, BuilderforceClient, type BuilderforceClientOptions, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatCompletionResponse, ChatCompletionStream, type ChatMessage, type ChatRole, type ContentPart, type EmbeddingObject, EmbeddingsApi, type EmbeddingsCreateParams, type EmbeddingsResponse, type FailoverEvent, type FunctionDefinition, type ImageGenerationCreateParams, type ImageGenerationDataEntry, type ImageGenerationResponse, type ImageUrlContentPart, ImagesApi, type JsonSchemaSpec, type ModelsListResponse, type PerCallOptions, type ResponseFormat, type TextContentPart, type ToolCall, type ToolCallDelta, type ToolCallFunction, type ToolChoice, type ToolSpec, type UsageByDay, type UsageByModel, type UsageByUser, type UsageGetParams, type UsageResponse };
|
|
633
|
+
export { type AiCapability, BuilderforceApiError, BuilderforceClient, type BuilderforceClientOptions, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatCompletionResponse, ChatCompletionStream, type ChatMessage, type ChatRole, type ContentPart, type EmbeddingObject, EmbeddingsApi, type EmbeddingsCreateParams, type EmbeddingsResponse, type FailoverEvent, type FunctionDefinition, type ImageGenerationCreateParams, type ImageGenerationDataEntry, type ImageGenerationResponse, type ImageUrlContentPart, ImagesApi, type JsonSchemaSpec, type ModelInfo, ModelsApi, type ModelsListResponse, type PerCallOptions, type ResponseFormat, type TextContentPart, type ToolCall, type ToolCallDelta, type ToolCallFunction, type ToolChoice, type ToolSpec, type UsageByDay, type UsageByModel, type UsageByUser, type UsageGetParams, type UsageResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -256,18 +256,46 @@ interface ChatCompletionResponse {
|
|
|
256
256
|
};
|
|
257
257
|
[key: string]: unknown;
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Shape capability a model supports — what kinds of request it can serve.
|
|
261
|
+
* `vision` — accepts image content blocks (`image_url`); reads images and
|
|
262
|
+
* page-rasterized PDFs.
|
|
263
|
+
* `ocr` — tuned for text extraction from images / documents.
|
|
264
|
+
* `tools` — honours `tools` / `tool_choice` round-trips.
|
|
265
|
+
* `structured_output` — reliably emits valid JSON / honours `json_schema`.
|
|
266
|
+
*
|
|
267
|
+
* Consumers that need to read images or PDFs (e.g. hired.video) should pick a
|
|
268
|
+
* model whose `capabilities` include `vision` or `ocr`. See
|
|
269
|
+
* `models.listImageCapable()` / `models.listOcr()`.
|
|
270
|
+
*/
|
|
271
|
+
type AiCapability = 'tools' | 'structured_output' | 'vision' | 'ocr';
|
|
272
|
+
/** One model in the tenant-plan pool, with availability + capability metadata. */
|
|
273
|
+
interface ModelInfo {
|
|
274
|
+
model: string;
|
|
275
|
+
vendor: string;
|
|
276
|
+
/** In the top "preferred" sub-pool the gateway round-robins across first. */
|
|
277
|
+
preferred: boolean;
|
|
278
|
+
/** Servable right now — key bound and not on per-model / per-vendor cooldown. */
|
|
279
|
+
available: boolean;
|
|
280
|
+
/** Epoch ms when the per-model cooldown lifts. Absent when not cooling. */
|
|
281
|
+
cooldownUntil?: number;
|
|
282
|
+
/** Epoch ms when the per-vendor cooldown lifts. Set when an upstream is wholesale-cooled. */
|
|
283
|
+
vendorCooledUntil?: number;
|
|
284
|
+
/** Whether the vendor's API key is bound. False → model is unservable. */
|
|
285
|
+
keyBound?: boolean;
|
|
286
|
+
/** Shape capabilities this model supports — drives image/PDF (`vision`/`ocr`),
|
|
287
|
+
* tool-calling (`tools`), and structured-output (`structured_output`) routing.
|
|
288
|
+
* Absent on older gateways that don't yet emit it. */
|
|
289
|
+
capabilities?: AiCapability[];
|
|
290
|
+
}
|
|
259
291
|
interface ModelsListResponse {
|
|
260
292
|
configured?: boolean;
|
|
261
293
|
object?: 'list';
|
|
262
294
|
product?: string;
|
|
263
295
|
effectivePlan?: string;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
preferred: boolean;
|
|
268
|
-
available: boolean;
|
|
269
|
-
cooldownUntil?: number;
|
|
270
|
-
}>;
|
|
296
|
+
/** Per-model pool status (present on the `configured: true` branch). */
|
|
297
|
+
data?: ModelInfo[];
|
|
298
|
+
/** Bare model-id pool (present on the `configured: false` branch). */
|
|
271
299
|
models?: string[];
|
|
272
300
|
[key: string]: unknown;
|
|
273
301
|
}
|
|
@@ -544,7 +572,37 @@ declare class ImagesApi {
|
|
|
544
572
|
declare class ModelsApi {
|
|
545
573
|
private readonly http;
|
|
546
574
|
constructor(http: HttpClient);
|
|
575
|
+
/** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */
|
|
547
576
|
list(): Promise<ModelsListResponse>;
|
|
577
|
+
/**
|
|
578
|
+
* Models in the tenant's plan pool, as structured entries. Empty when the
|
|
579
|
+
* gateway is unconfigured for this tenant (no `data` branch — nothing servable).
|
|
580
|
+
*/
|
|
581
|
+
listInfo(): Promise<ModelInfo[]>;
|
|
582
|
+
/**
|
|
583
|
+
* Models whose `capabilities` include `capability`. By default only
|
|
584
|
+
* currently-servable models are returned (`available: true`); pass
|
|
585
|
+
* `{ includeUnavailable: true }` to include cooled / key-unbound ones too.
|
|
586
|
+
*/
|
|
587
|
+
listByCapability(capability: AiCapability, opts?: {
|
|
588
|
+
includeUnavailable?: boolean;
|
|
589
|
+
}): Promise<ModelInfo[]>;
|
|
590
|
+
/**
|
|
591
|
+
* Models that can read images and (page-rasterized) PDFs — i.e. those with the
|
|
592
|
+
* `vision` OR `ocr` capability. This is the set a consumer that needs to ingest
|
|
593
|
+
* images / documents (e.g. hired.video) should pick from.
|
|
594
|
+
*/
|
|
595
|
+
listImageCapable(opts?: {
|
|
596
|
+
includeUnavailable?: boolean;
|
|
597
|
+
}): Promise<ModelInfo[]>;
|
|
598
|
+
/** Models tuned for text extraction from images / documents (`ocr` capability). */
|
|
599
|
+
listOcr(opts?: {
|
|
600
|
+
includeUnavailable?: boolean;
|
|
601
|
+
}): Promise<ModelInfo[]>;
|
|
602
|
+
/** Models that accept image content blocks (`vision` capability). */
|
|
603
|
+
listVision(opts?: {
|
|
604
|
+
includeUnavailable?: boolean;
|
|
605
|
+
}): Promise<ModelInfo[]>;
|
|
548
606
|
}
|
|
549
607
|
|
|
550
608
|
declare class UsageApi {
|
|
@@ -572,4 +630,4 @@ declare class BuilderforceClient {
|
|
|
572
630
|
constructor(options: BuilderforceClientOptions);
|
|
573
631
|
}
|
|
574
632
|
|
|
575
|
-
export { BuilderforceApiError, BuilderforceClient, type BuilderforceClientOptions, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatCompletionResponse, ChatCompletionStream, type ChatMessage, type ChatRole, type ContentPart, type EmbeddingObject, EmbeddingsApi, type EmbeddingsCreateParams, type EmbeddingsResponse, type FailoverEvent, type FunctionDefinition, type ImageGenerationCreateParams, type ImageGenerationDataEntry, type ImageGenerationResponse, type ImageUrlContentPart, ImagesApi, type JsonSchemaSpec, type ModelsListResponse, type PerCallOptions, type ResponseFormat, type TextContentPart, type ToolCall, type ToolCallDelta, type ToolCallFunction, type ToolChoice, type ToolSpec, type UsageByDay, type UsageByModel, type UsageByUser, type UsageGetParams, type UsageResponse };
|
|
633
|
+
export { type AiCapability, BuilderforceApiError, BuilderforceClient, type BuilderforceClientOptions, type ChatCompletionChunk, type ChatCompletionCreateParams, type ChatCompletionResponse, ChatCompletionStream, type ChatMessage, type ChatRole, type ContentPart, type EmbeddingObject, EmbeddingsApi, type EmbeddingsCreateParams, type EmbeddingsResponse, type FailoverEvent, type FunctionDefinition, type ImageGenerationCreateParams, type ImageGenerationDataEntry, type ImageGenerationResponse, type ImageUrlContentPart, ImagesApi, type JsonSchemaSpec, type ModelInfo, ModelsApi, type ModelsListResponse, type PerCallOptions, type ResponseFormat, type TextContentPart, type ToolCall, type ToolCallDelta, type ToolCallFunction, type ToolChoice, type ToolSpec, type UsageByDay, type UsageByModel, type UsageByUser, type UsageGetParams, type UsageResponse };
|
package/dist/index.mjs
CHANGED
|
@@ -133,9 +133,50 @@ var ModelsApi = class {
|
|
|
133
133
|
constructor(http) {
|
|
134
134
|
this.http = http;
|
|
135
135
|
}
|
|
136
|
+
/** Raw `/llm/v1/models` response — pool status, capabilities, plan, cooldowns. */
|
|
136
137
|
list() {
|
|
137
138
|
return this.http.getJson("/llm/v1/models");
|
|
138
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Models in the tenant's plan pool, as structured entries. Empty when the
|
|
142
|
+
* gateway is unconfigured for this tenant (no `data` branch — nothing servable).
|
|
143
|
+
*/
|
|
144
|
+
async listInfo() {
|
|
145
|
+
const res = await this.list();
|
|
146
|
+
return res.data ?? [];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Models whose `capabilities` include `capability`. By default only
|
|
150
|
+
* currently-servable models are returned (`available: true`); pass
|
|
151
|
+
* `{ includeUnavailable: true }` to include cooled / key-unbound ones too.
|
|
152
|
+
*/
|
|
153
|
+
async listByCapability(capability, opts) {
|
|
154
|
+
const includeUnavailable = opts?.includeUnavailable ?? false;
|
|
155
|
+
const all = await this.listInfo();
|
|
156
|
+
return all.filter(
|
|
157
|
+
(m) => (m.capabilities?.includes(capability) ?? false) && (includeUnavailable || m.available)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Models that can read images and (page-rasterized) PDFs — i.e. those with the
|
|
162
|
+
* `vision` OR `ocr` capability. This is the set a consumer that needs to ingest
|
|
163
|
+
* images / documents (e.g. hired.video) should pick from.
|
|
164
|
+
*/
|
|
165
|
+
async listImageCapable(opts) {
|
|
166
|
+
const includeUnavailable = opts?.includeUnavailable ?? false;
|
|
167
|
+
const all = await this.listInfo();
|
|
168
|
+
return all.filter(
|
|
169
|
+
(m) => ((m.capabilities?.includes("vision") ?? false) || (m.capabilities?.includes("ocr") ?? false)) && (includeUnavailable || m.available)
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
/** Models tuned for text extraction from images / documents (`ocr` capability). */
|
|
173
|
+
listOcr(opts) {
|
|
174
|
+
return this.listByCapability("ocr", opts);
|
|
175
|
+
}
|
|
176
|
+
/** Models that accept image content blocks (`vision` capability). */
|
|
177
|
+
listVision(opts) {
|
|
178
|
+
return this.listByCapability("vision", opts);
|
|
179
|
+
}
|
|
139
180
|
};
|
|
140
181
|
|
|
141
182
|
// src/application/UsageApi.ts
|
|
@@ -401,6 +442,7 @@ export {
|
|
|
401
442
|
BuilderforceClient,
|
|
402
443
|
ChatCompletionStream,
|
|
403
444
|
EmbeddingsApi,
|
|
404
|
-
ImagesApi
|
|
445
|
+
ImagesApi,
|
|
446
|
+
ModelsApi
|
|
405
447
|
};
|
|
406
448
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 { 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 list(): Promise<ModelsListResponse> {\n return this.http.getJson<ModelsListResponse>('/llm/v1/models');\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":";AAAA,gBAAuB,aACrB,QACkC;AAClC,QAAM,SAAS,OAAO,UAAU;AAChC,QAAM,UAAU,IAAI,YAAY;AAChC,MAAI,SAAS;AAEb,SAAO,MAAM;AACX,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,QAAI,KAAM;AAEV,cAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,UAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,aAAS,MAAM,IAAI,KAAK;AAExB,eAAW,QAAQ,OAAO;AACxB,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAQ,WAAW,QAAQ,EAAG;AAEnC,YAAM,OAAO,QAAQ,MAAM,CAAC,EAAE,KAAK;AACnC,UAAI,SAAS,SAAU;AAEvB,UAAI;AACF,cAAM,KAAK,MAAM,IAAI;AAAA,MACvB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACzBO,IAAM,uBAAN,MAAyE;AAAA,EAC7D;AAAA,EAEjB,YAAY,QAAoC;AAC9C,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,CAAC,OAAO,aAAa,IAAuD;AAC1E,WAAO,aAAkC,KAAK,MAAM;AAAA,EACtD;AAAA,EAEA,MAAM,SAA0B;AAC9B,QAAI,OAAO;AACX,qBAAiB,SAAS,MAAM;AAC9B,YAAM,QAAQ,MAAM,UAAU,CAAC,GAAG,OAAO;AACzC,UAAI,OAAO,UAAU,UAAU;AAC7B,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAOA,SAAS,sBAAsB,QAG7B;AACA,QAAM,EAAE,WAAW,QAAQ,gBAAgB,GAAG,KAAK,IAAI;AACvD,QAAM,UAAkC,CAAC;AACzC,MAAI,eAAgB,SAAQ,iBAAiB,IAAI;AACjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA,GAAI,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AAEO,IAAM,qBAAN,MAAyB;AAAA,EACb;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA,EAIA,MAAM,OACJ,QACwD;AACxD,UAAM,EAAE,MAAM,QAAQ,IAAI,sBAAsB,MAAM;AAEtD,QAAI,OAAO,QAAQ;AACjB,YAAM,WAAW,MAAM,KAAK,KAAK,QAAQ,4BAA4B,MAAM,OAAO;AAClF,UAAI,CAAC,SAAS,MAAM;AAClB,cAAM,IAAI,MAAM,oCAAoC;AAAA,MACtD;AACA,aAAO,IAAI,qBAAqB,SAAS,IAAI;AAAA,IAC/C;AAEA,WAAO,KAAK,KAAK,SAAiC,4BAA4B,MAAM,OAAO;AAAA,EAC7F;AACF;;;AClEA,SAASA,uBAAsB,QAG7B;AACA,QAAM,EAAE,WAAW,QAAQ,gBAAgB,GAAG,KAAK,IAAI;AACvD,QAAM,UAAkC,CAAC;AACzC,MAAI,eAAgB,SAAQ,iBAAiB,IAAI;AACjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA,GAAI,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACR;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QAA6D;AAClE,UAAM,EAAE,MAAM,QAAQ,IAAIA,uBAAsB,MAAM;AACtD,WAAO,KAAK,KAAK,SAA6B,sBAAsB,MAAM,OAAO;AAAA,EACnF;AACF;;;AC/BA,SAASC,uBAAsB,QAG7B;AACA,QAAM,EAAE,WAAW,QAAQ,gBAAgB,GAAG,KAAK,IAAI;AACvD,QAAM,UAAkC,CAAC;AACzC,MAAI,eAAgB,SAAQ,iBAAiB,IAAI;AACjD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA,GAAI,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,IACvD;AAAA,EACF;AACF;AAeO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,SAAS,QAAuE;AAC9E,UAAM,EAAE,MAAM,QAAQ,IAAIA,uBAAsB,MAAM;AACtD,WAAO,KAAK,KAAK,SAAkC,8BAA8B,MAAM,OAAO;AAAA,EAChG;AACF;;;AC9CO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,OAAoC;AAClC,WAAO,KAAK,KAAK,QAA4B,gBAAgB;AAAA,EAC/D;AACF;;;ACVO,IAAM,WAAN,MAAe;AAAA,EACH;AAAA,EAEjB,YAAY,MAAkB;AAC5B,SAAK,OAAO;AAAA,EACd;AAAA,EAEA,IAAI,SAAyB,CAAC,GAA2B;AACvD,UAAM,QAAQ,OAAO,OAAO,SAAS,WAAW,SAAS,mBAAmB,OAAO,OAAO,IAAI,CAAC,CAAC,KAAK;AACrG,WAAO,KAAK,KAAK,QAAuB,gBAAgB,KAAK,EAAE;AAAA,EACjE;AACF;;;ACZO,IAAM,uBAAN,cAAmC,MAAM;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA;AAAA,EAEhB,YACE,SACA,QACA,MACA,SACA,WACA,QACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AACZ,SAAK,UAAU;AACf,SAAK,YAAY;AACjB,SAAK,WAAW,QAAQ;AACxB,SAAK,aAAa,QAAQ;AAC1B,SAAK,SAAS,QAAQ;AACtB,SAAK,QAAQ,QAAQ;AAIrB,QAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,YAAM,IAAK,QAAoC;AAC/C,UAAI,MAAM,QAAQ,CAAC,GAAG;AACpB,cAAM,UAA2B,CAAC;AAClC,mBAAW,SAAS,GAAG;AACrB,cAAI,SAAS,OAAO,UAAU,UAAU;AACtC,kBAAM,IAAI;AACV,gBAAI,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,WAAW,YAAY,OAAO,EAAE,SAAS,UAAU;AAC7F,oBAAM,KAAK;AACX,sBAAQ,KAAK;AAAA,gBACX,OAAO,EAAE;AAAA,gBAAO,QAAQ,EAAE;AAAA,gBAAQ,MAAM,EAAE;AAAA,gBAC1C,GAAI,OAAO,GAAG,eAAe,WAAW,EAAE,YAAY,GAAG,WAAW,IAAI,CAAC;AAAA,gBACzE,GAAI,OAAO,GAAG,SAAS,WAAW,EAAE,MAAM,GAAG,KAAK,IAAI,CAAC;AAAA,cACzD,CAAC;AAAA,YACH;AAAA,UACF;AAAA,QACF;AACA,YAAI,QAAQ,SAAS,EAAG,MAAK,YAAY;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AACF;AAqBO,IAAM,aAAN,MAAiB;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAA4B;AACtC,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,QAAQ,QAAQ,OAAO,EAAE;AAMhD,UAAM,YAAY,QAAQ,WAAW;AACrC,SAAK,UAAU,UAAU,KAAK,UAAU;AAMxC,SAAK,mBAAmB,QAAQ,aAAa;AAAA,EAC/C;AAAA,EAEA,MAAM,QAAW,MAAc,SAAsC;AACnE,UAAM,MAAM,MAAM,KAAK,iBAAiB,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,KAAK,aAAa,OAAO;AAAA,IACpC,GAAG,OAAO;AACV,WAAO,KAAK,kBAAqB,GAAG;AAAA,EACtC;AAAA,EAEA,MAAM,SAAY,MAAc,MAAe,SAAsC;AACnF,UAAM,MAAM,MAAM,KAAK,iBAAiB,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,KAAK,aAAa,SAAS,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,MAC1E,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,GAAG,OAAO;AACV,WAAO,KAAK,kBAAqB,GAAG;AAAA,EACtC;AAAA,EAEA,MAAM,QAAQ,MAAc,MAAe,SAA6C;AACtF,UAAM,MAAM,MAAM,KAAK,iBAAiB,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,KAAK,aAAa,SAAS,EAAE,gBAAgB,mBAAmB,CAAC;AAAA,MAC1E,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,GAAG,OAAO;AACV,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,KAAK,WAAW,GAAG;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,aAAa,SAA0B,MAAuD;AACpG,WAAO;AAAA,MACL,eAAe,UAAU,KAAK,MAAM;AAAA,MACpC,GAAI,QAAQ,CAAC;AAAA,MACb,GAAI,SAAS,WAAW,CAAC;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,iBACZ,OACA,MACA,SACmB;AACnB,UAAM,YAAY,SAAS,aAAa,KAAK;AAC7C,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAI5D,UAAM,SAAS,eAAe,WAAW,QAAQ,SAAS,MAAM;AAEhE,QAAI;AACF,aAAO,MAAM,KAAK,QAAQ,OAAO,EAAE,GAAG,MAAM,OAAO,CAAC;AAAA,IACtD,SAAS,OAAO;AACd,UAAI,WAAW,OAAO,SAAS;AAC7B,cAAM,IAAI,qBAAqB,2BAA2B,SAAS,MAAM,KAAK,SAAS;AAAA,MACzF;AACA,UAAI,SAAS,QAAQ,SAAS;AAC5B,cAAM,IAAI,qBAAqB,6BAA6B,KAAK,SAAS;AAAA,MAC5E;AACA,YAAM;AAAA,IACR,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAc,kBAAqB,KAA2B;AAC5D,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,KAAK,WAAW,GAAG;AAAA,IACjC;AACA,WAAO,IAAI,KAAK;AAAA,EAClB;AAAA,EAEA,MAAc,WAAW,KAA8C;AACrE,UAAM,WAAW,mBAAmB,IAAI,MAAM;AAC9C,UAAM,YAAY,IAAI,QAAQ,IAAI,cAAc,KAAK;AAIrD,UAAM,mBAAmB,iBAAiB,IAAI,QAAQ,IAAI,aAAa,CAAC;AAExE,QAAI;AACF,YAAM,UAAU,MAAM,IAAI,KAAK;AAiB/B,YAAM,WACJ,YAAY,QACT,OAAO,YAAY,YACnB,OAAO,QAAQ,UAAU,YACzB,QAAQ,UAAU,OACjB,QAAQ,QACR;AAEN,YAAM,YACJ,YAAY,QACT,OAAO,YAAY,YACnB,QAAQ,YAAY,SACpB,aAAa;AAElB,YAAM,QAAS,aAAa,aAAa,OAAO,WAAW;AAW3D,YAAM,UACH,OAAO,OAAO,YAAY,YAAY,MAAM,WACzC,OAAO,OAAO,UAAU,YAAY,MAAM,SAC3C;AAKL,YAAM,OAAO,OAAO,OAAO,SAAS,WAAW,OAAO,MAAM,IAAI,IAAI,OAAO;AAO3E,YAAM,aAAc,OAAO,WAAW,OAAO,MAAM,YAAY,WAC3D,MAAM,UACN;AACJ,YAAM,SAAS,OAAO,OAAO,WAAW,WACpC,MAAM,SACL,OAAO,YAAY,WAAW,WAAW,WAAW,SAAS;AAClE,YAAM,QAAQ,OAAO,OAAO,UAAU,WAClC,MAAM,QACL,OAAO,YAAY,UAAU,WAC1B,WAAW,QACV,OAAO,YAAY,mBAAmB,WAAW,WAAW,iBAAiB;AAEtF,aAAO,IAAI;AAAA,QACT;AAAA,QACA,IAAI;AAAA,QACJ;AAAA,QACA,OAAO;AAAA,QACP;AAAA,QACA;AAAA,UACE,UAAY,OAAO;AAAA,UACnB,YAAY,oBAAoB,OAAO;AAAA,UACvC;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AACN,YAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,aAAO,IAAI;AAAA,QACT,QAAQ;AAAA,QACR,IAAI;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,QACA,qBAAqB,SAAY,EAAE,YAAY,iBAAiB,IAAI;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,GAAsC;AAC9D,MAAI,KAAK,KAAM,QAAO;AACtB,QAAM,IAAI,OAAO,CAAC;AAClB,SAAO,OAAO,SAAS,CAAC,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC,IAAI;AACxD;AAMA,SAAS,kBAAkB,SAAsD;AAC/E,QAAM,OAAO,QAAQ,OAAO,CAAC,MAAwB,MAAM,MAAS;AACpE,MAAI,KAAK,WAAW,EAAG,QAAO,KAAK,CAAC;AAEpC,QAAM,UAAW,YAA6E;AAC9F,MAAI,OAAO,YAAY,YAAY;AACjC,WAAO,QAAQ,IAAI;AAAA,EACrB;AAEA,QAAM,MAAM,IAAI,gBAAgB;AAChC,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,SAAS;AAAE,UAAI,MAAM,EAAE,MAAM;AAAG;AAAA,IAAO;AAC7C,MAAE,iBAAiB,SAAS,MAAM,IAAI,MAAM,EAAE,MAAM,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EACvE;AACA,SAAO,IAAI;AACb;;;AClUO,IAAM,qBAAN,MAAyB;AAAA,EACd;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEhB,YAAY,SAAoC;AAC9C,UAAM,SAAS,QAAQ,QAAQ,KAAK;AACpC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,WAAW;AAAA,MAC1B;AAAA,MACA,SAAS,QAAQ,WAAW;AAAA,MAC5B,SAAS,QAAQ;AAAA,MACjB,WAAW,QAAQ;AAAA,IACrB,CAAC;AAED,SAAK,OAAO;AAAA,MACV,aAAa,IAAI,mBAAmB,IAAI;AAAA,IAC1C;AACA,SAAK,aAAa,IAAI,cAAc,IAAI;AACxC,SAAK,SAAS,IAAI,UAAU,IAAI;AAChC,SAAK,SAAS,IAAI,UAAU,IAAI;AAChC,SAAK,QAAQ,IAAI,SAAS,IAAI;AAAA,EAChC;AACF;","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"],"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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seanhogg/builderforce-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2026.6.29",
|
|
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",
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"type-check": "tsc --noEmit",
|
|
51
51
|
"test": "vitest run",
|
|
52
52
|
"prepare": "npm run build",
|
|
53
|
-
"prepublishOnly": "npm run build"
|
|
53
|
+
"prepublishOnly": "npm run build",
|
|
54
|
+
"typecheck:native": "tsgo --noEmit"
|
|
54
55
|
},
|
|
55
56
|
"engines": {
|
|
56
57
|
"node": ">=18"
|
|
@@ -58,7 +59,8 @@
|
|
|
58
59
|
"devDependencies": {
|
|
59
60
|
"@types/node": "^24.7.2",
|
|
60
61
|
"tsup": "^8.5.0",
|
|
61
|
-
"typescript": "^
|
|
62
|
-
"vitest": "^4.0.3"
|
|
62
|
+
"typescript": "^6.0.3",
|
|
63
|
+
"vitest": "^4.0.3",
|
|
64
|
+
"@typescript/native-preview": "7.0.0-dev.20260627.2"
|
|
63
65
|
}
|
|
64
66
|
}
|