@combycode/llm-sdk 1.2.0 → 1.4.0

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.
@@ -0,0 +1,44 @@
1
+ /** Retrieve the bytes of a hosted-tool output file (e.g. code-execution files).
2
+ *
3
+ * A `FileOutput` may carry inline base64 `data` (Google), a `url` (OpenAI
4
+ * code-interpreter images), or an `id` fetched from the provider's files API
5
+ * (Anthropic, OpenAI container files). These helpers resolve all three into
6
+ * bytes — buffered (`retrieveFile` → Blob) or streamed (`streamFile` →
7
+ * ReadableStream, for large files piped straight to a sink).
8
+ *
9
+ * All HTTP flows through the injected `EngineFetch` (auth, queue, cost, traces). */
10
+ import type { EngineFetch } from '../../network/types';
11
+ import type { ProviderName } from '../types/provider';
12
+ import type { FileOutput } from '../types/response';
13
+ export interface RetrieveContext {
14
+ provider: ProviderName;
15
+ apiKey: string;
16
+ fetch: EngineFetch;
17
+ /** Provider API base URL; defaults per provider when omitted. */
18
+ baseURL?: string;
19
+ }
20
+ /** A retrieved file's bytes plus the metadata an end user needs to display and
21
+ * open it as an attachment (correct name + type). */
22
+ export interface RetrievedFile {
23
+ blob: Blob;
24
+ /** Filename (from Content-Disposition, else the FileOutput name). */
25
+ name?: string;
26
+ /** MIME type (from Content-Type, else the FileOutput mime). */
27
+ mimeType: string;
28
+ /** Size in bytes. */
29
+ size: number;
30
+ }
31
+ /** A streamed file: its byte stream plus best-effort metadata (from the response
32
+ * headers). The stream itself carries only bytes — name/type/size come from here. */
33
+ export interface FileStream {
34
+ stream: ReadableStream<Uint8Array>;
35
+ name?: string;
36
+ mimeType?: string;
37
+ /** From Content-Length, when the provider sends it. */
38
+ size?: number;
39
+ }
40
+ /** Fetch the whole file as bytes (buffered) plus its name/type/size. */
41
+ export declare function retrieveFile(file: FileOutput, ctx: RetrieveContext): Promise<RetrievedFile>;
42
+ /** Stream the file's bytes (un-buffered) plus best-effort name/type/size. Pipe the
43
+ * stream straight to a file, GridFS, or an HTTP response — nothing is buffered. */
44
+ export declare function streamFile(file: FileOutput, ctx: RetrieveContext): Promise<FileStream>;
@@ -26,5 +26,8 @@ export declare class AnthropicAdapter implements ProviderAdapter {
26
26
  private buildContentPart;
27
27
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
28
28
  parseStreamEvent(event: SSEEvent): StreamEvent[];
29
+ /** Stateless — every event is self-contained (code-execution result blocks
30
+ * arrive complete in a single content_block_start). */
31
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
29
32
  private parseUsage;
30
33
  }
@@ -0,0 +1,15 @@
1
+ /** Hosted server-side tools each provider's chat models support.
2
+ *
3
+ * This is ADAPTER-SOURCED: it reflects what the provider adapters actually map
4
+ * the unified `{ type: 'web_search' | 'code_interpreter' }` builtins onto (each
5
+ * to the provider's native shape), verified by the live tools-parity test. It is
6
+ * applied PROVIDER-LEVEL to tool-capable (chat-family) models at catalog load —
7
+ * a deliberate first pass; a reliable per-model source can refine it later.
8
+ *
9
+ * Coverage (verified live 2026-07):
10
+ * - web_search → anthropic, openai, google, xai, openrouter
11
+ * - code_interpreter → anthropic, openai, google, xai (NOT openrouter: it
12
+ * proxies function tools + its own plugins, but does not route hosted code
13
+ * execution — confirmed against the API). */
14
+ import type { ProviderName } from '../types/provider';
15
+ export declare const PROVIDER_BUILTIN_TOOLS: Record<ProviderName, readonly string[]>;
@@ -23,5 +23,12 @@ export declare class GoogleAdapter implements ProviderAdapter {
23
23
  private buildContent;
24
24
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
25
25
  parseStreamEvent(event: SSEEvent): StreamEvent[];
26
+ /** Stateful — Google splits the code-execution marker (`executableCode` /
27
+ * `codeExecutionResult`) and the produced file (`inlineData`) across parts and
28
+ * often across SSE events. The closure remembers "code execution began in this
29
+ * stream" so a later `inlineData` blob is routed to `files` (a code-exec
30
+ * artifact) rather than `media` (conversational output). */
31
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
32
+ private streamEvents;
26
33
  private parseUsage;
27
34
  }
@@ -26,5 +26,7 @@ export declare class GoogleInteractionsAdapter implements ProviderAdapter {
26
26
  enableStreaming(providerReq: ProviderHttpRequest): void;
27
27
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
28
28
  parseStreamEvent(event: SSEEvent): StreamEvent[];
29
+ /** Stateless — the Interactions adapter surfaces no code-execution file outputs. */
30
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
29
31
  private parseUsage;
30
32
  }
@@ -21,5 +21,7 @@ export declare class OpenAIAdapter implements ProviderAdapter {
21
21
  enableStreaming(providerReq: ProviderHttpRequest, _req: NormalizedRequest): void;
22
22
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
23
23
  parseStreamEvent(event: SSEEvent): StreamEvent[];
24
+ /** Stateless — Chat Completions has no hosted code-execution file outputs. */
25
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
24
26
  private parseUsage;
25
27
  }
@@ -5,7 +5,7 @@
5
5
  import type { SSEEvent } from '../../../network/types';
6
6
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
7
7
  import type { NormalizedRequest } from '../../types/request';
8
- import { type CompletionResponse, type Usage } from '../../types/response';
8
+ import { type CompletionResponse, type FileOutput, type Usage } from '../../types/response';
9
9
  import type { StreamEvent } from '../../types/stream';
10
10
  export interface OpenAIResponsesAdapterConfig {
11
11
  apiKey: string;
@@ -25,5 +25,12 @@ export declare class OpenAIResponsesAdapter implements ProviderAdapter {
25
25
  enableStreaming(providerReq: ProviderHttpRequest): void;
26
26
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
27
27
  parseStreamEvent(event: SSEEvent): StreamEvent[];
28
+ /** Stateless — each output item finalizes with all its file annotations in a
29
+ * single response.output_item.done event. */
30
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
31
+ /** Hosted code-execution output files from one output item. Overridable so
32
+ * Responses-compatible providers with a different file shape (e.g. xAI, which
33
+ * embeds files in the code-interpreter `logs` payload) can extend it. */
34
+ protected filesFromOutputItem(item: Record<string, unknown>): FileOutput[];
28
35
  protected parseUsage(u: Record<string, unknown> | undefined): Usage;
29
36
  }
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider';
9
9
  import type { NormalizedRequest } from '../../types/request';
10
+ import type { FileOutput } from '../../types/response';
10
11
  import { OpenAIResponsesAdapter } from '../openai/responses';
11
12
  export interface XAIResponsesAdapterConfig {
12
13
  apiKey: string;
@@ -17,4 +18,7 @@ export declare class XAIResponsesAdapter extends OpenAIResponsesAdapter {
17
18
  constructor(config: XAIResponsesAdapterConfig);
18
19
  baseURL(): string;
19
20
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
21
+ /** xAI embeds code-execution files inline in the `logs` payload — extend the base
22
+ * extraction (which handles OpenAI-style annotations / image URLs) with the xAI shape. */
23
+ protected filesFromOutputItem(item: Record<string, unknown>): FileOutput[];
20
24
  }
@@ -25,8 +25,16 @@ export interface ProviderAdapter {
25
25
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
26
26
  /** Parse provider's raw HTTP response body to a normalized CompletionResponse. */
27
27
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
28
- /** Convert one SSE event from the provider stream into zero or more StreamEvents. */
28
+ /** Convert one SSE event from the provider stream into zero or more StreamEvents.
29
+ * Stateless per-event primitive — see `createStreamParser` for the per-stream
30
+ * entry point the client actually calls. */
29
31
  parseStreamEvent(event: SSEEvent): StreamEvent[];
32
+ /** Create a per-stream parser. Returns a function that converts each SSE event
33
+ * into zero or more StreamEvents, holding any per-stream state (e.g. whether the
34
+ * turn has begun hosted code execution) in its closure. The client calls this
35
+ * once per `stream()` so each stream gets isolated state. Stateless adapters
36
+ * return `parseStreamEvent` bound to themselves. */
37
+ createStreamParser(): (event: SSEEvent) => StreamEvent[];
30
38
  /** Auth headers (Bearer / x-api-key / etc.). */
31
39
  authHeaders(): Record<string, string>;
32
40
  /** Base URL — provider's domain root. */
@@ -39,6 +39,10 @@ export interface FileOutput {
39
39
  url?: string;
40
40
  /** What produced it (e.g. 'code_execution'). */
41
41
  source?: string;
42
+ /** Provider-specific retrieval hints, used by `retrieveFile`/`streamFile` when the
43
+ * file is fetched by `id` (e.g. OpenAI container files set `{ containerId }`).
44
+ * Absent for providers that don't need extra context. */
45
+ ref?: Record<string, unknown>;
42
46
  }
43
47
  export type FinishReason = 'stop' | 'tool_use' | 'length' | 'content_filter' | 'error';
44
48
  export interface Usage {
@@ -1,6 +1,6 @@
1
1
  /** Universal streaming event types. */
2
2
  import type { ModerationEntry } from '../moderation/types';
3
- import type { Usage } from './response';
3
+ import type { FileOutput, Usage } from './response';
4
4
  export type MediaStreamType = 'image' | 'audio' | 'video';
5
5
  export type StreamEvent = {
6
6
  type: 'text';
@@ -41,6 +41,14 @@ export type StreamEvent = {
41
41
  type: 'media_end';
42
42
  mediaId?: string;
43
43
  }
44
+ /** A hosted-tool output file (e.g. a code-execution chart/CSV) became available.
45
+ * Carries the `FileOutput` descriptor (id / url / inline data + name / mimeType),
46
+ * not the bytes — fetch those via `retrieveFile` / `streamFile`. The same file
47
+ * is also collected onto the streamed final response's `files`. */
48
+ | {
49
+ type: 'file';
50
+ file: FileOutput;
51
+ }
44
52
  /** A moderation result for the input or output. `source` distinguishes a
45
53
  * provider-native result from a client-emulated one. Emitted by the moderation
46
54
  * option (report-only). */
@@ -15,8 +15,10 @@ export interface HttpRequest {
15
15
  model: string;
16
16
  /** How to parse the response body. Default 'json' (LLM responses, image-gen
17
17
  * with b64_json, video-status JSON). Use 'arraybuffer' for binary downloads
18
- * (TTS audio bytes, video file bytes). 'text' for plain-text responses. */
19
- responseType?: 'json' | 'arraybuffer' | 'text';
18
+ * (TTS audio bytes, video file bytes). 'text' for plain-text responses.
19
+ * 'stream' returns the raw `ReadableStream<Uint8Array>` body un-buffered (large
20
+ * file downloads that pipe straight to a sink). */
21
+ responseType?: 'json' | 'arraybuffer' | 'text' | 'stream';
20
22
  /** When the request body is already a Uint8Array / ArrayBuffer (binary
21
23
  * upload like multipart) the queue should NOT JSON.stringify it. Default
22
24
  * false → body is JSON.stringify'd. */
@@ -140,6 +140,11 @@ export declare class ModelCatalog {
140
140
  getPreferredApi(provider: string, model: string): ApiType | null;
141
141
  supportsApi(provider: string, model: string, api: ApiType): boolean;
142
142
  supportsTools(provider: string, model: string): boolean;
143
+ /** Hosted server-side builtin tools this model supports (e.g. `['web_search',
144
+ * 'code_interpreter']`). Empty when unknown or the model isn't tool-capable. */
145
+ builtinToolsFor(provider: string, model: string): string[];
146
+ /** Whether this model supports a specific hosted builtin tool. */
147
+ supportsBuiltinTool(provider: string, model: string, tool: string): boolean;
143
148
  supportsPreviousResponseId(provider: string, model: string): boolean;
144
149
  /** Server-state retention as a duration string ("30d", "72h"), or null if unsupported. */
145
150
  getStateRetention(provider: string, model: string): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@combycode/llm-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Unified, pluggable AI SDK for accessing the LLMs of every major provider (Anthropic, OpenAI, Google, xAI, OpenRouter) through one API. Cross-environment: Node, Bun, and the browser.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",