@aparte/provider-openai-compat 0.3.0-alpha.0 → 0.5.0-alpha.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.
package/README.md CHANGED
@@ -17,5 +17,11 @@ AparteConfig.registerAIProvider(createOpenAICompatProvider({ id: 'groq', baseURL
17
17
  `@aparte/core` is a **peer dependency**. For vendors outside the OpenAI-compat family
18
18
  (Anthropic, Gemini, …) use [`@aparte/provider-ai-sdk`](../ai-sdk) instead.
19
19
 
20
+ **Works on the server too.** Nothing here touches the DOM, and core resolves its DOM-free
21
+ entry under Node's export condition — so you can reuse this adapter from a backend, an
22
+ Electron main process or an SSR pass instead of re-writing one. Pair `buildRequest` /
23
+ `parseStream` with your own `fetch` when you already own the request (your `AbortSignal`,
24
+ your headers), or hand it to `createAparteChatHandler` from core.
25
+
20
26
  > Part of the [aparté](https://github.com/apartejs/aparte) monorepo. ESM-only.
21
27
  > See the **Providers** guide in the docs for the full usage.
package/dist/index.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * vendors outside this family (Anthropic, Gemini, …) use the AI-SDK bridge
19
19
  * provider instead — this package deliberately covers ONE format.
20
20
  */
21
- import type { AparteAIProvider, AparteAIModel, AparteAIProviderConfigSchema, AparteStreamEvent } from '@aparte/core';
21
+ import type { AparteAIProvider, AparteAIModel, AparteAIProviderConfigSchema, AparteStreamEvent, AparteFormatAdapter } from '@aparte/core';
22
22
  /** Config for one OpenAI-compatible endpoint. Everything but `id`/`baseURL` is branding/data. */
23
23
  export interface OpenAICompatProviderOptions {
24
24
  /** Provider id used across aparté (key resolution, model picker, events). */
@@ -52,6 +52,20 @@ export interface OpenAICompatProviderOptions {
52
52
  /** Override the default apiKey+endpoint settings schema. */
53
53
  configSchema?: AparteAIProviderConfigSchema;
54
54
  }
55
+ /**
56
+ * What {@link createOpenAICompatProvider} hands back: a provider whose whole
57
+ * format-adapter surface is guaranteed present.
58
+ *
59
+ * `AparteAIProvider` declares that surface optional — correct in general, since a
60
+ * provider may own its I/O through `chat()` instead — and `AparteFormatAdapter` keeps
61
+ * `authHeaders` / `parseText` optional because some vendors authenticate by query
62
+ * string or never answer non-streaming. THIS factory always supplies all of them, so
63
+ * saying so here is the difference between a caller writing
64
+ * `provider.buildRequest(req)` and a caller sprinkling `!` or writing a check that
65
+ * cannot fail. Useful to annotate with when you drive the adapter yourself (your own
66
+ * `fetch`, your own `AbortSignal`) from a server or an Electron main process.
67
+ */
68
+ export type OpenAICompatProvider = AparteAIProvider & AparteFormatAdapter & Required<Pick<AparteFormatAdapter, 'authHeaders' | 'parseText'>>;
55
69
  /**
56
70
  * Build an `AparteAIProvider` (full format-adapter surface) for one
57
71
  * OpenAI-compatible endpoint. Register it like any provider:
@@ -62,8 +76,10 @@ export interface OpenAICompatProviderOptions {
62
76
  * // or any compat endpoint, no preset needed:
63
77
  * AparteConfig.registerAIProvider(createOpenAICompatProvider({ id: 'groq', baseURL: 'https://api.groq.com/openai/v1' }));
64
78
  * ```
79
+ *
80
+ * Returns {@link OpenAICompatProvider}: the full format-adapter surface, non-optional.
65
81
  */
66
- export declare function createOpenAICompatProvider(opts: OpenAICompatProviderOptions): AparteAIProvider;
82
+ export declare function createOpenAICompatProvider(opts: OpenAICompatProviderOptions): OpenAICompatProvider;
67
83
  /**
68
84
  * OpenAI-compatible SSE stream parser — this package's own copy of core's
69
85
  * `parseOpenAIStream` (the parser follows the format adapter; core keeps only
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,4BAA4B,EAM5B,iBAAiB,EAEpB,MAAM,cAAc,CAAC;AAKtB,iGAAiG;AACjG,MAAM,WAAW,2BAA2B;IACxC,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,4BAA4B,CAAC;CAC/C;AA8DD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,gBAAgB,CA2F9F;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACnC,cAAc,CAAC,iBAAiB,CAAC,CAmHnC;AAED,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,4BAA4B,EAM5B,iBAAiB,EAEjB,mBAAmB,EACtB,MAAM,cAAc,CAAC;AAKtB,iGAAiG;AACjG,MAAM,WAAW,2BAA2B;IACxC,6EAA6E;IAC7E,EAAE,EAAE,MAAM,CAAC;IACX,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,4BAA4B,CAAC;CAC/C;AA8DD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GACd,mBAAmB,GACnB,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,oBAAoB,CA2FlG;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACnC,cAAc,CAAC,iBAAiB,CAAC,CA4HnC;AAED,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -195,7 +195,7 @@ function createOpenAICompatProvider(opts) {
195
195
  function parseOpenAICompatStream(stream) {
196
196
  const decoder = new TextDecoder();
197
197
  let buffer = "";
198
- const toolCallsById = {};
198
+ let toolCallsById = {};
199
199
  let capturedUsage;
200
200
  let reader = null;
201
201
  return new ReadableStream({
@@ -262,8 +262,7 @@ function parseOpenAICompatStream(stream) {
262
262
  const toolCall = { id: entry.id, name: entry.name, input };
263
263
  controller.enqueue({ type: "tool_use", ...toolCall });
264
264
  }
265
- controller.enqueue({ type: "done", usage: capturedUsage });
266
- return;
265
+ toolCallsById = {};
267
266
  }
268
267
  } catch {
269
268
  console.warn("[openai-compat] Skipped an unparseable SSE data line:", raw);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/presets.ts","../src/index.ts"],"sourcesContent":["/**\n * presets.ts — vendor DATA for well-known OpenAI-compatible endpoints.\n *\n * A preset is nothing but an `OpenAICompatProviderOptions` literal: base URL +\n * branding (icon/color/helpUrl) carried over from the retired per-vendor\n * packages. No code varies per vendor — that is the whole point.\n *\n * ```ts\n * AparteConfig.registerAIProvider(createOpenAICompatProvider(presets.MISTRAL));\n * ```\n *\n * Local servers (LM Studio, Ollama) are served through their OpenAI-compat\n * `/v1` endpoints — same format, `isLocal` just relaxes the key requirement.\n */\n\nimport type { OpenAICompatProviderOptions } from './index.js';\n\nconst OPENAI: OpenAICompatProviderOptions = {\n id: 'openai',\n baseURL: 'https://api.openai.com/v1',\n name: 'OpenAI',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8z\"/><path d=\"M12 6v6l4 2\"/></svg>`,\n color: '#10a37f',\n description: 'GPT-4o, GPT-4 Turbo, and the O1 reasoning models',\n helpUrl: 'https://platform.openai.com/api-keys',\n};\n\nconst MISTRAL: OpenAICompatProviderOptions = {\n id: 'mistral',\n baseURL: 'https://api.mistral.ai/v1',\n name: 'Mistral AI',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2l4 22-4-8-4 8 4-22z\"/></svg>`,\n color: '#fd7e14',\n description: 'European AI: Mistral Large, Mixtral, Codestral',\n helpUrl: 'https://console.mistral.ai/api-keys/',\n};\n\nconst ZAI: OpenAICompatProviderOptions = {\n id: 'zai',\n baseURL: 'https://open.bigmodel.cn/api/paas/v4',\n name: 'Z.ai (Zhipu)',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M13 2L3 14h9l-1 8 10-12h-9l1-8z\"/></svg>`,\n color: '#6366f1',\n description: \"Access Zhipu AI's free GLM models\",\n hasFreeModels: true,\n helpUrl: 'https://open.bigmodel.cn',\n};\n\nconst OPENROUTER: OpenAICompatProviderOptions = {\n id: 'openrouter',\n baseURL: 'https://openrouter.ai/api/v1',\n name: 'OpenRouter',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\"/><path d=\"M2 12h20\"/></svg>`,\n color: '#000000',\n description: 'Access 100+ AI models through a single API',\n hasFreeModels: true,\n helpUrl: 'https://openrouter.ai/keys',\n // OpenRouter attribution headers (sent on chat + model fetch).\n extraHeaders: {\n 'HTTP-Referer': typeof window !== 'undefined' ? window.location.origin : '',\n 'X-Title': 'aparté',\n },\n};\n\nconst LMSTUDIO: OpenAICompatProviderOptions = {\n id: 'lmstudio',\n baseURL: 'http://localhost:1234/v1',\n name: 'LM Studio',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M9 9h6v6H9z\"/><path d=\"M9 1h1v3h-1zM14 1h1v3h-1zM9 20h1v3h-1zM14 20h1v3h-1zM20 9h3v1h-3zM20 14h3v1h-3zM1 9h3v1H1zM1 14h3v1H1z\"/></svg>`,\n color: '#444444',\n description: 'Run LLMs locally with the LM Studio app',\n hasFreeModels: true,\n isLocal: true,\n helpUrl: 'https://lmstudio.ai/',\n};\n\n// Ollama through its OpenAI-compat endpoint (`/v1`), NOT its native `/api/chat`.\n// Same chat-completions format as everyone else; the native-protocol niceties\n// (inline base64 images, Ollama-shaped tool calls, keep_alive) don't apply —\n// see the README's Ollama section for the behavioural delta.\nconst OLLAMA: OpenAICompatProviderOptions = {\n id: 'ollama',\n baseURL: 'http://localhost:11434/v1',\n name: 'Ollama',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10 10 10 0 0 1-10-10 10 10 0 0 1 10-10zM12 11l4 2-4 2-4-2 4-2zM12 6v5M12 15v3\"/></svg>`,\n color: '#57534e',\n description: 'Run LLMs locally — free and fully private',\n hasFreeModels: true,\n isLocal: true,\n helpUrl: 'https://ollama.com/',\n};\n\n/** Vendor presets for {@link createOpenAICompatProvider}. Pure data. */\nexport const presets = { OPENAI, MISTRAL, ZAI, OPENROUTER, LMSTUDIO, OLLAMA } as const;\n","/**\n * @aparte/provider-openai-compat — ONE adapter for every OpenAI-compatible\n * chat-completions endpoint.\n *\n * The OpenAI `/chat/completions` wire format is the de-facto industry standard:\n * OpenAI, Mistral, OpenRouter, Z.ai, Groq, Together, LM Studio, Ollama (`/v1`)\n * and many more all speak it. This package is the single, zero-dependency\n * format adapter for that family — vendors differ only by DATA (base URL, auth\n * header, branding), which you pass as config (or pick from `presets`).\n *\n * It replaces the per-vendor `@aparte/provider-{openai,mistral,zai,openrouter,\n * lmstudio,ollama}` packages, whose adapter bodies were byte-identical copies\n * (drift even produced real bugs: LM Studio dropped `max_tokens`, Z.ai dropped\n * `seed` — both fixed here by construction, there is only one body now).\n *\n * Model lists are CONSUMER data: pass `models` statically, or rely on the\n * generic `GET {baseURL}/models` fetcher (part of the compat standard). For\n * vendors outside this family (Anthropic, Gemini, …) use the AI-SDK bridge\n * provider instead — this package deliberately covers ONE format.\n */\n\nimport type {\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigSchema,\n AparteChatRequest,\n AparteChatMessage,\n AparteTool,\n AparteToolCall,\n AparteContentPart,\n AparteStreamEvent,\n AparteUsage,\n} from '@aparte/core';\nimport { contentToText } from '@aparte/core';\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\n/** Config for one OpenAI-compatible endpoint. Everything but `id`/`baseURL` is branding/data. */\nexport interface OpenAICompatProviderOptions {\n /** Provider id used across aparté (key resolution, model picker, events). */\n id: string;\n /** Endpoint base, e.g. `https://api.openai.com/v1` or `http://localhost:11434/v1`. */\n baseURL: string;\n /** Display name (defaults to `id`). */\n name?: string;\n /** Brand icon (SVG string / data URI / icon-provider key). */\n icon?: string;\n /** Brand color. */\n color?: string;\n /** Short tag line. */\n description?: string;\n /** Where the user gets a key. */\n helpUrl?: string;\n /** Whether the vendor offers free models. */\n hasFreeModels?: boolean;\n /**\n * Local server (LM Studio, Ollama…): key optional, and the generic\n * `/models` fetch runs even without a key.\n */\n isLocal?: boolean;\n /** Static model list (consumer data). Defaults to `[]` — use `fetchModels`. */\n models?: AparteAIModel[];\n /**\n * Extra headers sent on every request (chat + model fetch), e.g.\n * OpenRouter's attribution headers `HTTP-Referer` / `X-Title`.\n */\n extraHeaders?: Record<string, string>;\n /** Override the default apiKey+endpoint settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n// ─── Message / tool shaping (the format half) ────────────────────────────────\n\n/** Content → OpenAI multipart array when images are present. */\nfunction toOpenAIContent(content: string | AparteContentPart[]): unknown {\n if (typeof content === 'string') return content;\n return content.map(p => {\n if (p.type === 'text') return { type: 'text', text: p.text };\n if (p.type === 'image') return { type: 'image_url', image_url: { url: p.image } };\n return { type: 'text', text: '' }; // AparteFilePart — no inline-file support in the compat format\n });\n}\n\n/** AparteChatMessage[] → OpenAI messages (incl. the tool_call / tool_result envelope). */\nfunction toOpenAIMessages(messages: AparteChatMessage[]): unknown[] {\n return messages.map(msg => {\n if (msg.role === 'tool_call') {\n return {\n role: 'assistant',\n content: msg.precedingText ?? null,\n tool_calls: (msg.toolCalls ?? []).map(tc => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.input) },\n })),\n };\n }\n if (msg.role === 'tool_result') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: contentToText(msg.content) };\n }\n return { role: msg.role, content: toOpenAIContent(msg.content) };\n });\n}\n\n/** AparteTool[] → OpenAI function-tool declarations. */\nfunction toOpenAITools(tools: AparteTool[]): unknown[] {\n return tools.map(t => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.inputSchema },\n }));\n}\n\n/** Accept both raw keys and user-pasted `Bearer xxx` values (local servers). */\nfunction bearer(key: string): string {\n return key.startsWith('Bearer ') ? key : `Bearer ${key}`;\n}\n\nconst DEFAULT_CONFIG_SCHEMA = (opts: OpenAICompatProviderOptions): AparteAIProviderConfigSchema => ({\n fields: opts.isLocal\n ? [\n { id: 'endpoint', type: 'url', label: 'Server', defaultValue: opts.baseURL, required: true },\n { id: 'apiKey', type: 'password', label: 'API Key / Token (optional)', placeholder: 'Bearer ...', isAdvanced: true },\n ]\n : [\n { id: 'apiKey', type: 'password', label: 'API Key', placeholder: 'sk-...', required: true },\n { id: 'endpoint', type: 'url', label: 'Custom endpoint', placeholder: opts.baseURL, isAdvanced: true },\n ],\n});\n\n// ─── The factory ─────────────────────────────────────────────────────────────\n\n/**\n * Build an `AparteAIProvider` (full format-adapter surface) for one\n * OpenAI-compatible endpoint. Register it like any provider:\n *\n * ```ts\n * import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';\n * AparteConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));\n * // or any compat endpoint, no preset needed:\n * AparteConfig.registerAIProvider(createOpenAICompatProvider({ id: 'groq', baseURL: 'https://api.groq.com/openai/v1' }));\n * ```\n */\nexport function createOpenAICompatProvider(opts: OpenAICompatProviderOptions): AparteAIProvider {\n const displayName = opts.name ?? opts.id;\n\n return {\n id: opts.id,\n\n getMetadata() {\n return {\n id: opts.id,\n name: displayName,\n icon: opts.icon,\n color: opts.color,\n description: opts.description,\n helpUrl: opts.helpUrl,\n hasFreeModels: opts.hasFreeModels,\n isLocal: opts.isLocal,\n configSchema: opts.configSchema ?? DEFAULT_CONFIG_SCHEMA(opts),\n };\n },\n\n getModels(): AparteAIModel[] {\n return opts.models ?? [];\n },\n\n /**\n * Generic `GET {baseURL}/models` — part of the compat standard. Cloud\n * endpoints need a key (returns `[]` without one); local servers fetch\n * keyless. Vendor-specific niceties (pricing, name prettifying) are\n * consumer concerns: pass `models` yourself for anything fancier.\n */\n async fetchModels(config?: string | Record<string, string>): Promise<AparteAIModel[]> {\n const apiKey = typeof config === 'string' ? config : config?.['apiKey'];\n const endpoint = (typeof config === 'object' ? config?.['endpoint'] : null) || opts.baseURL;\n if (!apiKey && !opts.isLocal) return [];\n\n try {\n const headers: Record<string, string> = { ...opts.extraHeaders };\n if (apiKey) headers['Authorization'] = bearer(apiKey);\n const response = await fetch(`${endpoint}/models`, { headers });\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const data = await response.json() as { data?: Array<{ id: string; name?: string; context_length?: number }> };\n return (data.data ?? []).map(m => ({\n id: m.id,\n name: m.name || m.id,\n contextWindow: m.context_length,\n capabilities: ['streaming'],\n }));\n } catch (error) {\n console.error(`[${displayName}] Failed to fetch models:`, error);\n return [];\n }\n },\n\n // ── Format-adapter surface (transport ⊥ format) ──────────────────────\n // The vendor concern only: request shape + stream parsing. Auth and\n // network are the transport's job (DirectTransport / BackendTransport).\n defaultEndpoint: opts.baseURL,\n\n buildRequest(request: AparteChatRequest) {\n const body: Record<string, unknown> = {\n model: request.modelId,\n messages: toOpenAIMessages(request.messages),\n temperature: request.temperature,\n max_tokens: request.maxTokens,\n stream: request.stream ?? true,\n ...((request.stream ?? true) ? { stream_options: { include_usage: true } } : {}),\n ...(request.seed !== undefined ? { seed: request.seed } : {}),\n };\n if (request.tools?.length) {\n body['tools'] = toOpenAITools(request.tools);\n body['tool_choice'] = 'auto';\n }\n return {\n path: '/chat/completions',\n body,\n ...(opts.extraHeaders ? { headers: opts.extraHeaders } : {}),\n };\n },\n\n authHeaders(key: string) {\n return { Authorization: bearer(key) };\n },\n\n parseStream(body: ReadableStream<Uint8Array>) {\n return parseOpenAICompatStream(body);\n },\n\n parseText(json: unknown): string {\n return (json as { choices?: Array<{ message?: { content?: string } }> })?.choices?.[0]?.message?.content || '';\n },\n };\n}\n\n// ─── SSE stream parser (ported from @aparte/core parseOpenAIStream) ──────────\n\n/**\n * OpenAI-compatible SSE stream parser — this package's own copy of core's\n * `parseOpenAIStream` (the parser follows the format adapter; core keeps only\n * the aparté-native NDJSON parser).\n *\n * Handles:\n * - `delta.content` → text event\n * - `delta.reasoning_content` → thinking event (Qwen3, DeepSeek R1, …)\n * - `delta.tool_calls` → accumulate → tool_use on finish_reason='tool_calls'\n * - usage-only chunk + [DONE] → done{usage}\n */\nexport function parseOpenAICompatStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<AparteStreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Tool call accumulation state (keyed by index)\n const toolCallsById: Record<number, { id: string; name: string; args: string }> = {};\n let capturedUsage: AparteUsage | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n reader = stream.getReader();\n try {\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 const raw = trimmed.slice(5).trim();\n if (raw === '[DONE]') {\n controller.enqueue({ type: 'done', usage: capturedUsage });\n return;\n }\n try {\n const json = JSON.parse(raw);\n\n // Capture usage from the usage-only chunk (choices: [])\n if (json.usage) {\n capturedUsage = {\n inputTokens: json.usage.prompt_tokens ?? 0,\n outputTokens: json.usage.completion_tokens ?? 0,\n totalTokens: json.usage.total_tokens,\n cacheReadTokens: json.usage.prompt_tokens_details?.cached_tokens,\n };\n }\n\n const choice = json.choices?.[0];\n if (!choice) continue;\n\n const delta = choice.delta;\n if (delta) {\n if (delta.reasoning_content) {\n controller.enqueue({ type: 'thinking', delta: delta.reasoning_content });\n }\n if (delta.content) {\n controller.enqueue({ type: 'text', delta: delta.content });\n }\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx: number = tc.index ?? 0;\n if (!toolCallsById[idx]) {\n toolCallsById[idx] = { id: tc.id ?? '', name: tc.function?.name ?? '', args: '' };\n }\n if (tc.id) toolCallsById[idx].id = tc.id;\n if (tc.function?.name) toolCallsById[idx].name = tc.function.name;\n if (tc.function?.arguments) toolCallsById[idx].args += tc.function.arguments;\n }\n }\n }\n\n // Emit tool_use events when the turn is done\n if (choice.finish_reason === 'tool_calls') {\n for (const entry of Object.values(toolCallsById)) {\n let input: Record<string, unknown> = {};\n // finish_reason is 'tool_calls' → the model considers the call\n // COMPLETE, so a parse failure here is malformed tool-call JSON\n // (a real small-model failure), not partial streaming. Surface it\n // instead of silently handing the tool `{}`.\n try {\n input = JSON.parse(entry.args);\n } catch {\n console.warn(\n `[openai-compat] Tool \"${entry.name}\" returned malformed arguments JSON; ` +\n `passing empty input. Raw:`, entry.args,\n );\n }\n const toolCall: AparteToolCall = { id: entry.id, name: entry.name, input };\n controller.enqueue({ type: 'tool_use', ...toolCall });\n }\n controller.enqueue({ type: 'done', usage: capturedUsage });\n return;\n }\n } catch {\n // Every line here is a complete, newline-terminated SSE\n // line (see buffer split above), so a parse failure is an\n // unexpected/malformed server payload, not partial JSON —\n // log a breadcrumb instead of dropping it silently.\n console.warn('[openai-compat] Skipped an unparseable SSE data line:', raw);\n }\n }\n }\n controller.enqueue({ type: 'done', usage: capturedUsage });\n } catch (err: unknown) {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n } finally {\n reader?.releaseLock();\n reader = null;\n controller.close();\n }\n },\n // Consumer cancelled (e.g. user hit \"stop\"): cancel the underlying reader so\n // the vendor response body stops being drained to its natural end instead of\n // silently finishing the whole SSE stream in the background.\n cancel(reason) {\n const r = reader;\n reader = null;\n return r?.cancel(reason);\n },\n });\n}\n\nexport * from './presets.js';\nexport type { AparteAIProvider, AparteAIModel } from '@aparte/core';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/presets.ts","../src/index.ts"],"sourcesContent":["/**\n * presets.ts — vendor DATA for well-known OpenAI-compatible endpoints.\n *\n * A preset is nothing but an `OpenAICompatProviderOptions` literal: base URL +\n * branding (icon/color/helpUrl) carried over from the retired per-vendor\n * packages. No code varies per vendor — that is the whole point.\n *\n * ```ts\n * AparteConfig.registerAIProvider(createOpenAICompatProvider(presets.MISTRAL));\n * ```\n *\n * Local servers (LM Studio, Ollama) are served through their OpenAI-compat\n * `/v1` endpoints — same format, `isLocal` just relaxes the key requirement.\n */\n\nimport type { OpenAICompatProviderOptions } from './index.js';\n\nconst OPENAI: OpenAICompatProviderOptions = {\n id: 'openai',\n baseURL: 'https://api.openai.com/v1',\n name: 'OpenAI',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8z\"/><path d=\"M12 6v6l4 2\"/></svg>`,\n color: '#10a37f',\n description: 'GPT-4o, GPT-4 Turbo, and the O1 reasoning models',\n helpUrl: 'https://platform.openai.com/api-keys',\n};\n\nconst MISTRAL: OpenAICompatProviderOptions = {\n id: 'mistral',\n baseURL: 'https://api.mistral.ai/v1',\n name: 'Mistral AI',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2l4 22-4-8-4 8 4-22z\"/></svg>`,\n color: '#fd7e14',\n description: 'European AI: Mistral Large, Mixtral, Codestral',\n helpUrl: 'https://console.mistral.ai/api-keys/',\n};\n\nconst ZAI: OpenAICompatProviderOptions = {\n id: 'zai',\n baseURL: 'https://open.bigmodel.cn/api/paas/v4',\n name: 'Z.ai (Zhipu)',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M13 2L3 14h9l-1 8 10-12h-9l1-8z\"/></svg>`,\n color: '#6366f1',\n description: \"Access Zhipu AI's free GLM models\",\n hasFreeModels: true,\n helpUrl: 'https://open.bigmodel.cn',\n};\n\nconst OPENROUTER: OpenAICompatProviderOptions = {\n id: 'openrouter',\n baseURL: 'https://openrouter.ai/api/v1',\n name: 'OpenRouter',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\"/><path d=\"M2 12h20\"/></svg>`,\n color: '#000000',\n description: 'Access 100+ AI models through a single API',\n hasFreeModels: true,\n helpUrl: 'https://openrouter.ai/keys',\n // OpenRouter attribution headers (sent on chat + model fetch).\n extraHeaders: {\n 'HTTP-Referer': typeof window !== 'undefined' ? window.location.origin : '',\n 'X-Title': 'aparté',\n },\n};\n\nconst LMSTUDIO: OpenAICompatProviderOptions = {\n id: 'lmstudio',\n baseURL: 'http://localhost:1234/v1',\n name: 'LM Studio',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M9 9h6v6H9z\"/><path d=\"M9 1h1v3h-1zM14 1h1v3h-1zM9 20h1v3h-1zM14 20h1v3h-1zM20 9h3v1h-3zM20 14h3v1h-3zM1 9h3v1H1zM1 14h3v1H1z\"/></svg>`,\n color: '#444444',\n description: 'Run LLMs locally with the LM Studio app',\n hasFreeModels: true,\n isLocal: true,\n helpUrl: 'https://lmstudio.ai/',\n};\n\n// Ollama through its OpenAI-compat endpoint (`/v1`), NOT its native `/api/chat`.\n// Same chat-completions format as everyone else; the native-protocol niceties\n// (inline base64 images, Ollama-shaped tool calls, keep_alive) don't apply —\n// see the README's Ollama section for the behavioural delta.\nconst OLLAMA: OpenAICompatProviderOptions = {\n id: 'ollama',\n baseURL: 'http://localhost:11434/v1',\n name: 'Ollama',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10 10 10 0 0 1-10-10 10 10 0 0 1 10-10zM12 11l4 2-4 2-4-2 4-2zM12 6v5M12 15v3\"/></svg>`,\n color: '#57534e',\n description: 'Run LLMs locally — free and fully private',\n hasFreeModels: true,\n isLocal: true,\n helpUrl: 'https://ollama.com/',\n};\n\n/** Vendor presets for {@link createOpenAICompatProvider}. Pure data. */\nexport const presets = { OPENAI, MISTRAL, ZAI, OPENROUTER, LMSTUDIO, OLLAMA } as const;\n","/**\n * @aparte/provider-openai-compat — ONE adapter for every OpenAI-compatible\n * chat-completions endpoint.\n *\n * The OpenAI `/chat/completions` wire format is the de-facto industry standard:\n * OpenAI, Mistral, OpenRouter, Z.ai, Groq, Together, LM Studio, Ollama (`/v1`)\n * and many more all speak it. This package is the single, zero-dependency\n * format adapter for that family — vendors differ only by DATA (base URL, auth\n * header, branding), which you pass as config (or pick from `presets`).\n *\n * It replaces the per-vendor `@aparte/provider-{openai,mistral,zai,openrouter,\n * lmstudio,ollama}` packages, whose adapter bodies were byte-identical copies\n * (drift even produced real bugs: LM Studio dropped `max_tokens`, Z.ai dropped\n * `seed` — both fixed here by construction, there is only one body now).\n *\n * Model lists are CONSUMER data: pass `models` statically, or rely on the\n * generic `GET {baseURL}/models` fetcher (part of the compat standard). For\n * vendors outside this family (Anthropic, Gemini, …) use the AI-SDK bridge\n * provider instead — this package deliberately covers ONE format.\n */\n\nimport type {\n AparteAIProvider,\n AparteAIModel,\n AparteAIProviderConfigSchema,\n AparteChatRequest,\n AparteChatMessage,\n AparteTool,\n AparteToolCall,\n AparteContentPart,\n AparteStreamEvent,\n AparteUsage,\n AparteFormatAdapter,\n} from '@aparte/core';\nimport { contentToText } from '@aparte/core';\n\n// ─── Options ─────────────────────────────────────────────────────────────────\n\n/** Config for one OpenAI-compatible endpoint. Everything but `id`/`baseURL` is branding/data. */\nexport interface OpenAICompatProviderOptions {\n /** Provider id used across aparté (key resolution, model picker, events). */\n id: string;\n /** Endpoint base, e.g. `https://api.openai.com/v1` or `http://localhost:11434/v1`. */\n baseURL: string;\n /** Display name (defaults to `id`). */\n name?: string;\n /** Brand icon (SVG string / data URI / icon-provider key). */\n icon?: string;\n /** Brand color. */\n color?: string;\n /** Short tag line. */\n description?: string;\n /** Where the user gets a key. */\n helpUrl?: string;\n /** Whether the vendor offers free models. */\n hasFreeModels?: boolean;\n /**\n * Local server (LM Studio, Ollama…): key optional, and the generic\n * `/models` fetch runs even without a key.\n */\n isLocal?: boolean;\n /** Static model list (consumer data). Defaults to `[]` — use `fetchModels`. */\n models?: AparteAIModel[];\n /**\n * Extra headers sent on every request (chat + model fetch), e.g.\n * OpenRouter's attribution headers `HTTP-Referer` / `X-Title`.\n */\n extraHeaders?: Record<string, string>;\n /** Override the default apiKey+endpoint settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n// ─── Message / tool shaping (the format half) ────────────────────────────────\n\n/** Content → OpenAI multipart array when images are present. */\nfunction toOpenAIContent(content: string | AparteContentPart[]): unknown {\n if (typeof content === 'string') return content;\n return content.map(p => {\n if (p.type === 'text') return { type: 'text', text: p.text };\n if (p.type === 'image') return { type: 'image_url', image_url: { url: p.image } };\n return { type: 'text', text: '' }; // AparteFilePart — no inline-file support in the compat format\n });\n}\n\n/** AparteChatMessage[] → OpenAI messages (incl. the tool_call / tool_result envelope). */\nfunction toOpenAIMessages(messages: AparteChatMessage[]): unknown[] {\n return messages.map(msg => {\n if (msg.role === 'tool_call') {\n return {\n role: 'assistant',\n content: msg.precedingText ?? null,\n tool_calls: (msg.toolCalls ?? []).map(tc => ({\n id: tc.id,\n type: 'function',\n function: { name: tc.name, arguments: JSON.stringify(tc.input) },\n })),\n };\n }\n if (msg.role === 'tool_result') {\n return { role: 'tool', tool_call_id: msg.toolCallId, content: contentToText(msg.content) };\n }\n return { role: msg.role, content: toOpenAIContent(msg.content) };\n });\n}\n\n/** AparteTool[] → OpenAI function-tool declarations. */\nfunction toOpenAITools(tools: AparteTool[]): unknown[] {\n return tools.map(t => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.inputSchema },\n }));\n}\n\n/** Accept both raw keys and user-pasted `Bearer xxx` values (local servers). */\nfunction bearer(key: string): string {\n return key.startsWith('Bearer ') ? key : `Bearer ${key}`;\n}\n\nconst DEFAULT_CONFIG_SCHEMA = (opts: OpenAICompatProviderOptions): AparteAIProviderConfigSchema => ({\n fields: opts.isLocal\n ? [\n { id: 'endpoint', type: 'url', label: 'Server', defaultValue: opts.baseURL, required: true },\n { id: 'apiKey', type: 'password', label: 'API Key / Token (optional)', placeholder: 'Bearer ...', isAdvanced: true },\n ]\n : [\n { id: 'apiKey', type: 'password', label: 'API Key', placeholder: 'sk-...', required: true },\n { id: 'endpoint', type: 'url', label: 'Custom endpoint', placeholder: opts.baseURL, isAdvanced: true },\n ],\n});\n\n// ─── The factory ─────────────────────────────────────────────────────────────\n\n/**\n * What {@link createOpenAICompatProvider} hands back: a provider whose whole\n * format-adapter surface is guaranteed present.\n *\n * `AparteAIProvider` declares that surface optional — correct in general, since a\n * provider may own its I/O through `chat()` instead — and `AparteFormatAdapter` keeps\n * `authHeaders` / `parseText` optional because some vendors authenticate by query\n * string or never answer non-streaming. THIS factory always supplies all of them, so\n * saying so here is the difference between a caller writing\n * `provider.buildRequest(req)` and a caller sprinkling `!` or writing a check that\n * cannot fail. Useful to annotate with when you drive the adapter yourself (your own\n * `fetch`, your own `AbortSignal`) from a server or an Electron main process.\n */\nexport type OpenAICompatProvider =\n AparteAIProvider\n & AparteFormatAdapter\n & Required<Pick<AparteFormatAdapter, 'authHeaders' | 'parseText'>>;\n\n/**\n * Build an `AparteAIProvider` (full format-adapter surface) for one\n * OpenAI-compatible endpoint. Register it like any provider:\n *\n * ```ts\n * import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';\n * AparteConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));\n * // or any compat endpoint, no preset needed:\n * AparteConfig.registerAIProvider(createOpenAICompatProvider({ id: 'groq', baseURL: 'https://api.groq.com/openai/v1' }));\n * ```\n *\n * Returns {@link OpenAICompatProvider}: the full format-adapter surface, non-optional.\n */\nexport function createOpenAICompatProvider(opts: OpenAICompatProviderOptions): OpenAICompatProvider {\n const displayName = opts.name ?? opts.id;\n\n return {\n id: opts.id,\n\n getMetadata() {\n return {\n id: opts.id,\n name: displayName,\n icon: opts.icon,\n color: opts.color,\n description: opts.description,\n helpUrl: opts.helpUrl,\n hasFreeModels: opts.hasFreeModels,\n isLocal: opts.isLocal,\n configSchema: opts.configSchema ?? DEFAULT_CONFIG_SCHEMA(opts),\n };\n },\n\n getModels(): AparteAIModel[] {\n return opts.models ?? [];\n },\n\n /**\n * Generic `GET {baseURL}/models` — part of the compat standard. Cloud\n * endpoints need a key (returns `[]` without one); local servers fetch\n * keyless. Vendor-specific niceties (pricing, name prettifying) are\n * consumer concerns: pass `models` yourself for anything fancier.\n */\n async fetchModels(config?: string | Record<string, string>): Promise<AparteAIModel[]> {\n const apiKey = typeof config === 'string' ? config : config?.['apiKey'];\n const endpoint = (typeof config === 'object' ? config?.['endpoint'] : null) || opts.baseURL;\n if (!apiKey && !opts.isLocal) return [];\n\n try {\n const headers: Record<string, string> = { ...opts.extraHeaders };\n if (apiKey) headers['Authorization'] = bearer(apiKey);\n const response = await fetch(`${endpoint}/models`, { headers });\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n const data = await response.json() as { data?: Array<{ id: string; name?: string; context_length?: number }> };\n return (data.data ?? []).map(m => ({\n id: m.id,\n name: m.name || m.id,\n contextWindow: m.context_length,\n capabilities: ['streaming'],\n }));\n } catch (error) {\n console.error(`[${displayName}] Failed to fetch models:`, error);\n return [];\n }\n },\n\n // ── Format-adapter surface (transport ⊥ format) ──────────────────────\n // The vendor concern only: request shape + stream parsing. Auth and\n // network are the transport's job (DirectTransport / BackendTransport).\n defaultEndpoint: opts.baseURL,\n\n buildRequest(request: AparteChatRequest) {\n const body: Record<string, unknown> = {\n model: request.modelId,\n messages: toOpenAIMessages(request.messages),\n temperature: request.temperature,\n max_tokens: request.maxTokens,\n stream: request.stream ?? true,\n ...((request.stream ?? true) ? { stream_options: { include_usage: true } } : {}),\n ...(request.seed !== undefined ? { seed: request.seed } : {}),\n };\n if (request.tools?.length) {\n body['tools'] = toOpenAITools(request.tools);\n body['tool_choice'] = 'auto';\n }\n return {\n path: '/chat/completions',\n body,\n ...(opts.extraHeaders ? { headers: opts.extraHeaders } : {}),\n };\n },\n\n authHeaders(key: string) {\n return { Authorization: bearer(key) };\n },\n\n parseStream(body: ReadableStream<Uint8Array>) {\n return parseOpenAICompatStream(body);\n },\n\n parseText(json: unknown): string {\n return (json as { choices?: Array<{ message?: { content?: string } }> })?.choices?.[0]?.message?.content || '';\n },\n };\n}\n\n// ─── SSE stream parser (ported from @aparte/core parseOpenAIStream) ──────────\n\n/**\n * OpenAI-compatible SSE stream parser — this package's own copy of core's\n * `parseOpenAIStream` (the parser follows the format adapter; core keeps only\n * the aparté-native NDJSON parser).\n *\n * Handles:\n * - `delta.content` → text event\n * - `delta.reasoning_content` → thinking event (Qwen3, DeepSeek R1, …)\n * - `delta.tool_calls` → accumulate → tool_use on finish_reason='tool_calls'\n * - usage-only chunk + [DONE] → done{usage}\n */\nexport function parseOpenAICompatStream(\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<AparteStreamEvent> {\n const decoder = new TextDecoder();\n let buffer = '';\n\n // Tool call accumulation state (keyed by index)\n let toolCallsById: Record<number, { id: string; name: string; args: string }> = {};\n let capturedUsage: AparteUsage | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n\n return new ReadableStream<AparteStreamEvent>({\n async start(controller) {\n reader = stream.getReader();\n try {\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 const raw = trimmed.slice(5).trim();\n if (raw === '[DONE]') {\n controller.enqueue({ type: 'done', usage: capturedUsage });\n return;\n }\n try {\n const json = JSON.parse(raw);\n\n // Capture usage from the usage-only chunk (choices: [])\n if (json.usage) {\n capturedUsage = {\n inputTokens: json.usage.prompt_tokens ?? 0,\n outputTokens: json.usage.completion_tokens ?? 0,\n totalTokens: json.usage.total_tokens,\n cacheReadTokens: json.usage.prompt_tokens_details?.cached_tokens,\n };\n }\n\n const choice = json.choices?.[0];\n if (!choice) continue;\n\n const delta = choice.delta;\n if (delta) {\n if (delta.reasoning_content) {\n controller.enqueue({ type: 'thinking', delta: delta.reasoning_content });\n }\n if (delta.content) {\n controller.enqueue({ type: 'text', delta: delta.content });\n }\n if (delta.tool_calls) {\n for (const tc of delta.tool_calls) {\n const idx: number = tc.index ?? 0;\n if (!toolCallsById[idx]) {\n toolCallsById[idx] = { id: tc.id ?? '', name: tc.function?.name ?? '', args: '' };\n }\n if (tc.id) toolCallsById[idx].id = tc.id;\n if (tc.function?.name) toolCallsById[idx].name = tc.function.name;\n if (tc.function?.arguments) toolCallsById[idx].args += tc.function.arguments;\n }\n }\n }\n\n // Emit tool_use events when the turn is done\n if (choice.finish_reason === 'tool_calls') {\n for (const entry of Object.values(toolCallsById)) {\n let input: Record<string, unknown> = {};\n // finish_reason is 'tool_calls' → the model considers the call\n // COMPLETE, so a parse failure here is malformed tool-call JSON\n // (a real small-model failure), not partial streaming. Surface it\n // instead of silently handing the tool `{}`.\n try {\n input = JSON.parse(entry.args);\n } catch {\n console.warn(\n `[openai-compat] Tool \"${entry.name}\" returned malformed arguments JSON; ` +\n `passing empty input. Raw:`, entry.args,\n );\n }\n const toolCall: AparteToolCall = { id: entry.id, name: entry.name, input };\n controller.enqueue({ type: 'tool_use', ...toolCall });\n }\n // Do NOT emit `done` or return here: under\n // `include_usage` (which buildRequest requests) the\n // usage-only chunk arrives AFTER this finish chunk, so\n // stopping now dropped usage on every tool-call turn —\n // i.e. most turns of an agent. Keep reading; the single\n // `done` comes from `[DONE]` below, or from the\n // end-of-stream fallback if the socket just closes.\n // The map is cleared so a server that repeats the finish\n // chunk can't re-emit the same calls now that we stay in\n // the loop.\n toolCallsById = {};\n }\n } catch {\n // Every line here is a complete, newline-terminated SSE\n // line (see buffer split above), so a parse failure is an\n // unexpected/malformed server payload, not partial JSON —\n // log a breadcrumb instead of dropping it silently.\n console.warn('[openai-compat] Skipped an unparseable SSE data line:', raw);\n }\n }\n }\n controller.enqueue({ type: 'done', usage: capturedUsage });\n } catch (err: unknown) {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n } finally {\n reader?.releaseLock();\n reader = null;\n controller.close();\n }\n },\n // Consumer cancelled (e.g. user hit \"stop\"): cancel the underlying reader so\n // the vendor response body stops being drained to its natural end instead of\n // silently finishing the whole SSE stream in the background.\n cancel(reason) {\n const r = reader;\n reader = null;\n return r?.cancel(reason);\n },\n });\n}\n\nexport * from './presets.js';\nexport type { AparteAIProvider, AparteAIModel } from '@aparte/core';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aparte/provider-openai-compat",
3
- "version": "0.3.0-alpha.0",
3
+ "version": "0.5.0-alpha.0",
4
4
  "description": "One OpenAI-compatible chat-completions adapter for any /v1 endpoint — OpenAI, Mistral, OpenRouter, Z.ai, Groq, LM Studio, Ollama and friends, via presets or plain config.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -24,14 +24,14 @@
24
24
  "node": ">=18"
25
25
  },
26
26
  "peerDependencies": {
27
- "@aparte/core": "0.3.0-alpha.0"
27
+ "@aparte/core": "0.5.0-alpha.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^22.0.0",
31
31
  "typescript": "^5.4.0",
32
32
  "vite": "^6.0.0",
33
33
  "vite-plugin-dts": "^4.5.4",
34
- "@aparte/core": "0.3.0-alpha.0"
34
+ "@aparte/core": "0.5.0-alpha.0"
35
35
  },
36
36
  "keywords": [
37
37
  "ai",