@seanhogg/builderforce-sdk 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -248,7 +248,7 @@ try {
248
248
 
249
249
  | Option | Meaning |
250
250
  |---|---|
251
- | `timeoutMs` | Override client-level timeout for this call. Combined with `signal` (below) — whichever fires first wins. |
251
+ | `timeoutMs` | Override client-level timeout for this call. Combined with `signal` (below) — whichever fires first wins. **This is the *whole-request* budget**; the gateway enforces a separate ~25s per-vendor-call timeout so a single slow vendor doesn't eat the whole budget. With the default 60s `timeoutMs`, the gateway can typically try 2 candidate models before the SDK aborts. |
252
252
  | `signal` | Caller's `AbortSignal` for user-cancellable generation. |
253
253
  | `idempotencyKey` | Sent as `Idempotency-Key` header. Gateway 409s on replay within 10 min so retries can no-op safely. (Response-body cache replay is planned.) |
254
254
 
@@ -306,8 +306,8 @@ try {
306
306
  | 408 | `timeout` | SDK-side timeout fired |
307
307
  | 499 | `aborted` | Caller's `AbortSignal` aborted |
308
308
  | 409 | `idempotent_replay` | `Idempotency-Key` was used within the last 10 min — treat as no-op |
309
- | 429 | `plan_token_limit_exceeded` | Tenant hit daily plan budget |
310
- | 429 | `claw_token_limit_exceeded` | Per-claw daily cap exceeded (`clk_*` keys only) |
309
+ | 429 | `plan_token_limit_exceeded` | Tenant hit daily plan budget. **`error.terminal === true`** — don't retry on a different model. |
310
+ | 429 | `claw_token_limit_exceeded` | Per-claw daily cap exceeded (`clk_*` keys only). **`error.terminal === true`** — same caveat. |
311
311
  | 403 | `origin_not_authorized` | Browser request from an origin not in the key's allowlist (or key has no allowlist — server-only) |
312
312
  | 503 | (no code) | Vendor key not configured for the active plan tier |
313
313
  | 401 | `missing_api_key` | Auth issues |
@@ -315,6 +315,28 @@ try {
315
315
 
316
316
  `error.requestId` comes from the gateway's `x-request-id` header — quote it in support tickets. Map gateway 429s to your own 503 + alerting (it's an ops issue, not a user issue).
317
317
 
318
+ ### Don't retry terminal errors
319
+
320
+ Some failures will not resolve by retrying on a different model — most notably daily token-cap exhaustion, which is per-tenant. The gateway sets `error.terminal === true` on those responses; well-behaved fallback chains should short-circuit:
321
+
322
+ ```ts
323
+ async function callWithFallback(profile: Array<{ model: string; ... }>) {
324
+ for (const attempt of profile) {
325
+ try {
326
+ return await client.chat.completions.create({ model: attempt.model, ... });
327
+ } catch (err) {
328
+ if (err instanceof BuilderforceApiError) {
329
+ if (err.terminal) throw err; // cap exhausted — different model won't help
330
+ if (err.code === 'aborted') throw err;
331
+ // else: retry on next model in profile
332
+ }
333
+ }
334
+ }
335
+ }
336
+ ```
337
+
338
+ `error.retryAfter` (seconds) accompanies cap-exhaustion errors so you can sleep precisely until the next UTC midnight reset rather than polling. The same value is on the `Retry-After` response header.
339
+
318
340
  ## Models and usage
319
341
 
320
342
  ```ts
package/dist/index.cjs CHANGED
@@ -160,13 +160,24 @@ var BuilderforceApiError = class extends Error {
160
160
  code;
161
161
  details;
162
162
  requestId;
163
- constructor(message, status, code, details, requestId) {
163
+ /**
164
+ * `true` when the gateway has signalled this error will not resolve by
165
+ * retrying on a different model — e.g. plan or per-claw daily token cap
166
+ * exhausted (those caps are per-tenant, not per-model). Consumer-side
167
+ * fallback chains should short-circuit when this is set.
168
+ */
169
+ terminal;
170
+ /** Seconds the consumer should wait before retrying — server-supplied. */
171
+ retryAfter;
172
+ constructor(message, status, code, details, requestId, extras) {
164
173
  super(message);
165
174
  this.name = "BuilderforceApiError";
166
175
  this.status = status;
167
176
  this.code = code;
168
177
  this.details = details;
169
178
  this.requestId = requestId;
179
+ this.terminal = extras?.terminal;
180
+ this.retryAfter = extras?.retryAfter;
170
181
  }
171
182
  };
172
183
  var HttpClient = class {
@@ -247,15 +258,38 @@ var HttpClient = class {
247
258
  async toApiError(res) {
248
259
  const fallback = `Request failed (${res.status})`;
249
260
  const requestId = res.headers.get("x-request-id") ?? void 0;
261
+ const headerRetryAfter = parsePositiveInt(res.headers.get("retry-after"));
250
262
  try {
251
263
  const payload = await res.json();
252
- return new BuilderforceApiError(payload.error ?? fallback, res.status, payload.code, payload.details, requestId);
264
+ return new BuilderforceApiError(
265
+ payload.error ?? fallback,
266
+ res.status,
267
+ payload.code,
268
+ payload.details,
269
+ requestId,
270
+ {
271
+ terminal: payload.terminal,
272
+ retryAfter: headerRetryAfter ?? payload.retryAfter
273
+ }
274
+ );
253
275
  } catch {
254
276
  const text = await res.text().catch(() => "");
255
- return new BuilderforceApiError(text || fallback, res.status, void 0, void 0, requestId);
277
+ return new BuilderforceApiError(
278
+ text || fallback,
279
+ res.status,
280
+ void 0,
281
+ void 0,
282
+ requestId,
283
+ headerRetryAfter !== void 0 ? { retryAfter: headerRetryAfter } : void 0
284
+ );
256
285
  }
257
286
  }
258
287
  };
288
+ function parsePositiveInt(s) {
289
+ if (s == null) return void 0;
290
+ const n = Number(s);
291
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : void 0;
292
+ }
259
293
  function combineSignals(...signals) {
260
294
  const live = signals.filter((s) => s !== void 0);
261
295
  if (live.length === 1) return live[0];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.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 // Models / usage\n ModelsListResponse,\n UsageByModel,\n UsageByDay,\n UsageByUser,\n UsageResponse,\n UsageGetParams,\n // Embeddings\n EmbeddingsCreateParams,\n EmbeddingsResponse,\n EmbeddingObject,\n} from './domain/types';\n\nexport { ChatCompletionStream } from './application/ChatCompletionsApi';\nexport { EmbeddingsApi } from './application/EmbeddingsApi';\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 { 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","export 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 constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string) {\n super(message);\n this.name = 'BuilderforceApiError';\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\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 this.defaultTimeoutMs = options.timeoutMs ?? 60_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 try {\n const payload = await res.json() as { error?: string; code?: string; details?: unknown };\n return new BuilderforceApiError(payload.error ?? fallback, res.status, payload.code, payload.details, requestId);\n } catch {\n const text = await res.text().catch(() => '');\n return new BuilderforceApiError(text || fallback, res.status, undefined, undefined, requestId);\n }\n }\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 { 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 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.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n"],"mappings":"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","names":["splitTransportOptions"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.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 // Models / usage\n ModelsListResponse,\n UsageByModel,\n UsageByDay,\n UsageByUser,\n UsageResponse,\n UsageGetParams,\n // Embeddings\n EmbeddingsCreateParams,\n EmbeddingsResponse,\n EmbeddingObject,\n} from './domain/types';\n\nexport { ChatCompletionStream } from './application/ChatCompletionsApi';\nexport { EmbeddingsApi } from './application/EmbeddingsApi';\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 { 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","export 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 constructor(\n message: string,\n status: number,\n code?: string,\n details?: unknown,\n requestId?: string,\n extras?: { terminal?: boolean; retryAfter?: number },\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 }\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 this.defaultTimeoutMs = options.timeoutMs ?? 60_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 {\n error?: string;\n code?: string;\n details?: unknown;\n terminal?: boolean;\n retryAfter?: number;\n };\n return new BuilderforceApiError(\n payload.error ?? fallback,\n res.status,\n payload.code,\n payload.details,\n requestId,\n {\n terminal: payload.terminal,\n retryAfter: headerRetryAfter ?? payload.retryAfter,\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 { 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 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.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n"],"mappings":"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","names":["splitTransportOptions"]}
package/dist/index.d.cts CHANGED
@@ -294,7 +294,19 @@ declare class BuilderforceApiError extends Error {
294
294
  readonly code?: string;
295
295
  readonly details?: unknown;
296
296
  readonly requestId?: string;
297
- constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string);
297
+ /**
298
+ * `true` when the gateway has signalled this error will not resolve by
299
+ * retrying on a different model — e.g. plan or per-claw daily token cap
300
+ * exhausted (those caps are per-tenant, not per-model). Consumer-side
301
+ * fallback chains should short-circuit when this is set.
302
+ */
303
+ readonly terminal?: boolean;
304
+ /** Seconds the consumer should wait before retrying — server-supplied. */
305
+ readonly retryAfter?: number;
306
+ constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string, extras?: {
307
+ terminal?: boolean;
308
+ retryAfter?: number;
309
+ });
298
310
  }
299
311
  interface HttpClientOptions {
300
312
  apiKey: string;
package/dist/index.d.ts CHANGED
@@ -294,7 +294,19 @@ declare class BuilderforceApiError extends Error {
294
294
  readonly code?: string;
295
295
  readonly details?: unknown;
296
296
  readonly requestId?: string;
297
- constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string);
297
+ /**
298
+ * `true` when the gateway has signalled this error will not resolve by
299
+ * retrying on a different model — e.g. plan or per-claw daily token cap
300
+ * exhausted (those caps are per-tenant, not per-model). Consumer-side
301
+ * fallback chains should short-circuit when this is set.
302
+ */
303
+ readonly terminal?: boolean;
304
+ /** Seconds the consumer should wait before retrying — server-supplied. */
305
+ readonly retryAfter?: number;
306
+ constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string, extras?: {
307
+ terminal?: boolean;
308
+ retryAfter?: number;
309
+ });
298
310
  }
299
311
  interface HttpClientOptions {
300
312
  apiKey: string;
package/dist/index.mjs CHANGED
@@ -131,13 +131,24 @@ var BuilderforceApiError = class extends Error {
131
131
  code;
132
132
  details;
133
133
  requestId;
134
- constructor(message, status, code, details, requestId) {
134
+ /**
135
+ * `true` when the gateway has signalled this error will not resolve by
136
+ * retrying on a different model — e.g. plan or per-claw daily token cap
137
+ * exhausted (those caps are per-tenant, not per-model). Consumer-side
138
+ * fallback chains should short-circuit when this is set.
139
+ */
140
+ terminal;
141
+ /** Seconds the consumer should wait before retrying — server-supplied. */
142
+ retryAfter;
143
+ constructor(message, status, code, details, requestId, extras) {
135
144
  super(message);
136
145
  this.name = "BuilderforceApiError";
137
146
  this.status = status;
138
147
  this.code = code;
139
148
  this.details = details;
140
149
  this.requestId = requestId;
150
+ this.terminal = extras?.terminal;
151
+ this.retryAfter = extras?.retryAfter;
141
152
  }
142
153
  };
143
154
  var HttpClient = class {
@@ -218,15 +229,38 @@ var HttpClient = class {
218
229
  async toApiError(res) {
219
230
  const fallback = `Request failed (${res.status})`;
220
231
  const requestId = res.headers.get("x-request-id") ?? void 0;
232
+ const headerRetryAfter = parsePositiveInt(res.headers.get("retry-after"));
221
233
  try {
222
234
  const payload = await res.json();
223
- return new BuilderforceApiError(payload.error ?? fallback, res.status, payload.code, payload.details, requestId);
235
+ return new BuilderforceApiError(
236
+ payload.error ?? fallback,
237
+ res.status,
238
+ payload.code,
239
+ payload.details,
240
+ requestId,
241
+ {
242
+ terminal: payload.terminal,
243
+ retryAfter: headerRetryAfter ?? payload.retryAfter
244
+ }
245
+ );
224
246
  } catch {
225
247
  const text = await res.text().catch(() => "");
226
- return new BuilderforceApiError(text || fallback, res.status, void 0, void 0, requestId);
248
+ return new BuilderforceApiError(
249
+ text || fallback,
250
+ res.status,
251
+ void 0,
252
+ void 0,
253
+ requestId,
254
+ headerRetryAfter !== void 0 ? { retryAfter: headerRetryAfter } : void 0
255
+ );
227
256
  }
228
257
  }
229
258
  };
259
+ function parsePositiveInt(s) {
260
+ if (s == null) return void 0;
261
+ const n = Number(s);
262
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : void 0;
263
+ }
230
264
  function combineSignals(...signals) {
231
265
  const live = signals.filter((s) => s !== void 0);
232
266
  if (live.length === 1) return live[0];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.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 { 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","export 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 constructor(message: string, status: number, code?: string, details?: unknown, requestId?: string) {\n super(message);\n this.name = 'BuilderforceApiError';\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\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 this.defaultTimeoutMs = options.timeoutMs ?? 60_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 try {\n const payload = await res.json() as { error?: string; code?: string; details?: unknown };\n return new BuilderforceApiError(payload.error ?? fallback, res.status, payload.code, payload.details, requestId);\n } catch {\n const text = await res.text().catch(() => '');\n return new BuilderforceApiError(text || fallback, res.status, undefined, undefined, requestId);\n }\n }\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 { 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 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.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n"],"mappings":"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","names":["splitTransportOptions"]}
1
+ {"version":3,"sources":["../src/infrastructure/sse.ts","../src/application/ChatCompletionsApi.ts","../src/application/EmbeddingsApi.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 { 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","export 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 constructor(\n message: string,\n status: number,\n code?: string,\n details?: unknown,\n requestId?: string,\n extras?: { terminal?: boolean; retryAfter?: number },\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 }\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 this.defaultTimeoutMs = options.timeoutMs ?? 60_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 {\n error?: string;\n code?: string;\n details?: unknown;\n terminal?: boolean;\n retryAfter?: number;\n };\n return new BuilderforceApiError(\n payload.error ?? fallback,\n res.status,\n payload.code,\n payload.details,\n requestId,\n {\n terminal: payload.terminal,\n retryAfter: headerRetryAfter ?? payload.retryAfter,\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 { 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 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.models = new ModelsApi(http);\n this.usage = new UsageApi(http);\n }\n}\n"],"mappings":"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","names":["splitTransportOptions"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanhogg/builderforce-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Typed SDK for the Builderforce.ai LLM gateway — chat completions with tool-calling and structured output, embeddings, models, and usage analytics over an OpenAI-compatible surface.",
5
5
  "license": "MIT",
6
6
  "author": "Sean Hogg",