@aparte/provider-openai-compat 0.28.0 → 0.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,aAAa,EACb,4BAA4B,EAM5B,iBAAiB,EAGjB,mBAAmB,EACtB,MAAM,cAAc,CAAC;AAMtB,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;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IACrD,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,4BAA4B,CAAC;CAC/C;AAqQD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GACd,mBAAmB,GACnB,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE,aAAa,GAAG,WAAW,GAAG,eAAe,CAAC,CAAC,CAAC;AAEzF;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,oBAAoB,CAuKlG;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACnC,cAAc,CAAC,iBAAiB,CAAC,CA+QnC;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,EAGjB,mBAAmB,EACtB,MAAM,cAAc,CAAC;AAMtB,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;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;IACrD,+EAA+E;IAC/E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,4BAA4B,CAAC;CAC/C;AA4QD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,oBAAoB,GAC5B,gBAAgB,GACd,mBAAmB,GACnB,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE,aAAa,GAAG,WAAW,GAAG,eAAe,CAAC,CAAC,CAAC;AAEzF;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,oBAAoB,CAuKlG;AAID;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACnC,cAAc,CAAC,iBAAiB,CAAC,CA+QnC;AAED,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -332,9 +332,11 @@ function asJson(v) {
332
332
  function asJsonList(v) {
333
333
  return Array.isArray(v) ? v.map(asJson) : [];
334
334
  }
335
+ class HttpError extends Error {
336
+ }
335
337
  async function getJSON(url, headers) {
336
338
  const response = await fetch(url, { headers });
337
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
339
+ if (!response.ok) throw new HttpError(`HTTP ${response.status}`);
338
340
  return asJson(await response.json());
339
341
  }
340
342
  async function probeWindows(server, origin, headers) {
@@ -343,7 +345,8 @@ async function probeWindows(server, origin, headers) {
343
345
  let data;
344
346
  try {
345
347
  data = await getJSON(`${origin}/api/v1/models`, headers);
346
- } catch {
348
+ } catch (error) {
349
+ if (!(error instanceof HttpError)) throw error;
347
350
  const legacy = await getJSON(`${origin}/api/v0/models`, headers);
348
351
  for (const m of asJsonList(legacy["data"])) {
349
352
  const n = positiveInt(m["loaded_context_length"]);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/sources.ts","../src/presets.ts","../src/index.ts"],"sourcesContent":["/**\n * sources.ts — the sources a reply cites, read off the two shapes a compat endpoint sends.\n *\n * OpenAI and OpenRouter attach `url_citation` annotations to the message, with the\n * offsets of the passage they support. Perplexity attaches no offsets: it sends the list\n * of sources at the root of the chunk and writes `[1]`, `[2]` into the text itself.\n * Both end in the same `sources` event, emitted once before `done`, because the offsets\n * are into the text the whole call streamed and only mean something once it is complete.\n *\n * What is read here is passed through as data. A `javascript:` URL in an annotation\n * leaves as it arrived: refusing it is the renderer's job (the sanitizer's URL policy),\n * and a provider that filtered would be a second, silent policy.\n */\n\nimport type { AparteSource, AparteCitation } from '@aparte/core';\n\n/** The event {@link createSourceCollector} builds: what `AparteStreamEventMap['sources']` carries. */\nexport interface SourcesEvent {\n type: 'sources';\n sources: AparteSource[];\n citations?: AparteCitation[];\n}\n\n/** A non-empty string, or nothing: the wire sends `null` and `''` for what it does not have. */\nconst str = (value: unknown): string | undefined => (typeof value === 'string' && value ? value : undefined);\n\n/** `[n]` as Perplexity writes it in the text: 1-based into its list of sources. */\nconst MARKER = /\\[(\\d{1,3})\\]/g;\n\n/**\n * Where `text` holds code, as CommonMark reads it: a fence runs to a closing fence of its\n * character at least as long (or to the end), an indented line after a blank one is code,\n * and a backquote span closes on the same run within its paragraph — `\\`` opens nothing.\n */\nfunction codeRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let at = 0;\n let fence: { char: string; length: number; start: number } | null = null;\n let indented = -1;\n let blank = true;\n for (const line of text.split('\\n')) {\n const next = at + line.length + 1;\n if (fence) {\n const close = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/.exec(line);\n if (close && close[1]![0] === fence.char && close[1]!.length >= fence.length) {\n ranges.push([fence.start, next]);\n fence = null;\n }\n } else {\n const open = /^ {0,3}(`{3,}|~{3,})/.exec(line);\n const code = /^(?: {4}|\\t)/.test(line) && line.trim() !== '' && (blank || indented >= 0);\n if ((open || !code) && indented >= 0 && line.trim() !== '') {\n ranges.push([indented, at]);\n indented = -1;\n }\n if (open) fence = { char: open[1]![0]!, length: open[1]!.length, start: at };\n else if (code && indented < 0) indented = at;\n }\n blank = line.trim() === '';\n at = next;\n }\n if (fence) ranges.push([fence.start, text.length]);\n if (indented >= 0) ranges.push([indented, text.length]);\n const inside = (i: number): boolean => ranges.some(([from, to]) => i >= from && i < to);\n for (const m of text.matchAll(/(?<![\\\\`])(`+)(?!`)((?:(?!\\n[ \\t]*\\n)[\\s\\S])*?[^`])\\1(?!`)/g)) {\n const i = m.index ?? 0;\n if (!inside(i)) ranges.push([i, i + m[0].length]);\n }\n return ranges;\n}\n\n/**\n * Collects annotations and Perplexity's root fields as chunks arrive and builds the\n * `sources` event once the text is complete. One collector per stream, i.e. per call.\n */\nexport function createSourceCollector() {\n const sources: AparteSource[] = [];\n /** De-duplicated by URL: the same page cited for two passages is one source. */\n const byUrl = new Map<string, AparteSource>();\n /** Annotated passages as the wire gave them, clamped when the text length is known. */\n const annotated: Array<{ start: unknown; end: unknown; id: string }> = [];\n /** Perplexity's fields: the LAST chunk that carries one wins, whatever came before. */\n let urlList: unknown;\n let searchResults: unknown;\n\n /** The id of the source for this URL/title, minting one the first time. `undefined` when there is nothing to show. */\n const register = (url: string | undefined, title: string | undefined, snippet: string | undefined): string | undefined => {\n if (url === undefined && title === undefined) return undefined;\n const known = url !== undefined ? byUrl.get(url) : undefined;\n if (known) {\n // A later mention can carry what the first one lacked.\n known.title ??= title;\n known.snippet ??= snippet;\n return known.id;\n }\n const source: AparteSource = {\n id: `s${sources.length + 1}`,\n ...(url !== undefined ? { url } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(snippet !== undefined ? { snippet } : {}),\n };\n sources.push(source);\n if (url !== undefined) byUrl.set(url, source);\n return source.id;\n };\n\n return {\n /**\n * `delta.annotations` / `message.annotations`: `{ type: 'url_citation', url_citation:\n * { start_index, end_index, url, title, content? } }`. An entry of another kind\n * (`file_citation`, …) carries no `url_citation` and is skipped.\n */\n addAnnotations(list: unknown): void {\n if (!Array.isArray(list)) return;\n for (const entry of list) {\n const cited = (entry as { url_citation?: unknown } | null)?.url_citation;\n if (!cited || typeof cited !== 'object') continue;\n const c = cited as Record<string, unknown>;\n const id = register(str(c['url']), str(c['title']), str(c['content']));\n if (id !== undefined) annotated.push({ start: c['start_index'], end: c['end_index'], id });\n }\n },\n\n /** The root-level `citations: string[]` and `search_results: [{ title, url, snippet? }]`. */\n addListed(citations: unknown, results: unknown): void {\n if (Array.isArray(citations) && citations.length) urlList = citations;\n if (Array.isArray(results) && results.length) searchResults = results;\n },\n\n /**\n * The `sources` event for a call whose text is `text`, or `undefined` when nothing\n * was cited — a reply with no annotations and no list leaves the stream untouched.\n */\n finish(text: string): SourcesEvent | undefined {\n const citations: AparteCitation[] = [];\n\n // An offset the wire got wrong (past the end, negative, not a number) is\n // brought back into the text, or the passage is dropped: it is still a source.\n const within = (value: unknown): number | undefined => (\n typeof value === 'number' && Number.isFinite(value) ? Math.min(Math.max(Math.trunc(value), 0), text.length) : undefined\n );\n for (const { start: from, end: to, id } of annotated) {\n const end = within(to);\n // An end at the very start (or before it, brought back) is no passage.\n if (end === undefined || end <= 0) continue;\n const s = within(from);\n const start = s !== undefined && s < end ? s : undefined;\n // Two annotations on one passage are one citation with two sources (\"+1\").\n const same = citations.find(c => c.start === start && c.end === end);\n if (same) {\n if (!same.sources.includes(id)) same.sources.push(id);\n } else {\n citations.push({ ...(start !== undefined ? { start } : {}), end, sources: [id] });\n }\n }\n\n // Perplexity: the richer `search_results` when it sent them, else the bare URLs.\n const results = Array.isArray(searchResults) ? searchResults : [];\n const listed: Array<string | undefined> = results.length\n ? results.map(r => register(\n str((r as { url?: unknown } | null)?.url),\n str((r as { title?: unknown } | null)?.title),\n str((r as { snippet?: unknown } | null)?.snippet),\n ))\n : (Array.isArray(urlList) ? urlList : []).map(u => register(str(u), undefined, undefined));\n if (listed.length) {\n // Code is not prose: an `arr[1]` in a code span or a fence is no marker.\n const code = codeRanges(text);\n for (const m of text.matchAll(MARKER)) {\n const id = listed[Number(m[1]) - 1];\n if (id === undefined) continue;\n const start = m.index ?? 0;\n if (code.some(([a, b]) => start >= a && start < b)) continue;\n citations.push({ start, end: start + m[0].length, sources: [id], marker: true });\n }\n }\n\n if (!sources.length) return undefined;\n return { type: 'sources', sources, ...(citations.length ? { citations } : {}) };\n },\n };\n}\n","/**\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 * aparteGlobalConfig.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\n// Perplexity's Sonar models answer from a web search and cite what they read: the sources\n// arrive as `citations` / `search_results` on the chunk and the text carries `[1]`, `[2]`\n// markers, which this adapter turns into a `sources` event. There is no `GET /models` to\n// ask, so the list is declared here and both `getModels()` and `fetchModels()` serve it.\n// The icon is generic line art (a magnifier), not their mark.\nconst PERPLEXITY: OpenAICompatProviderOptions = {\n id: 'perplexity',\n baseURL: 'https://api.perplexity.ai',\n name: 'Perplexity',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>`,\n color: '#20808d',\n description: 'Sonar models that search the web and cite their sources',\n helpUrl: 'https://www.perplexity.ai/settings/api',\n listsModels: false,\n models: [\n { id: 'sonar', name: 'Sonar', capabilities: ['streaming'] },\n { id: 'sonar-pro', name: 'Sonar Pro', capabilities: ['streaming'] },\n { id: 'sonar-reasoning-pro', name: 'Sonar Reasoning Pro', capabilities: ['streaming'] },\n { id: 'sonar-deep-research', name: 'Sonar Deep Research', capabilities: ['streaming'] },\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 server: 'lmstudio',\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 server: 'ollama',\n helpUrl: 'https://ollama.com/',\n};\n\n// llama.cpp's `llama-server` and vLLM both serve `/v1/chat/completions` on their own\n// port. The icons are generic line art (a chip, a stack of layers), not either project's mark.\nconst LLAMACPP: OpenAICompatProviderOptions = {\n id: 'llamacpp',\n baseURL: 'http://localhost:8080/v1',\n name: 'llama.cpp',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M7 9h2M7 12h6M7 15h4\"/></svg>`,\n color: '#64748b',\n description: 'Run GGUF models locally with llama-server',\n hasFreeModels: true,\n isLocal: true,\n server: 'llamacpp',\n helpUrl: 'https://github.com/ggml-org/llama.cpp',\n};\n\nconst VLLM: OpenAICompatProviderOptions = {\n id: 'vllm',\n baseURL: 'http://localhost:8000/v1',\n name: 'vLLM',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3l9 5-9 5-9-5 9-5z\"/><path d=\"M3 13l9 5 9-5\"/></svg>`,\n color: '#6b7280',\n description: 'Serve open models from your own GPU with vLLM',\n hasFreeModels: true,\n isLocal: true,\n server: 'vllm',\n helpUrl: 'https://docs.vllm.ai/',\n};\n\n/** Vendor presets for {@link createOpenAICompatProvider}. Pure data. */\nexport const presets = { OPENAI, MISTRAL, ZAI, OPENROUTER, PERPLEXITY, LMSTUDIO, OLLAMA, LLAMACPP, VLLM } 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 AparteFinishReason,\n AparteFormatAdapter,\n} from '@aparte/core';\nimport { AparteErrorCode, contentToText } from '@aparte/core';\nimport { createSourceCollector } from './sources.js';\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 /**\n * `false` when the endpoint answers no `GET /models` (Perplexity): `fetchModels` then\n * serves `models` instead of asking, rather than logging a failed request.\n */\n listsModels?: boolean;\n /**\n * Which local server this endpoint is, so `fetchModels` can read the context window\n * the server actually serves through that server's own API: LM Studio's\n * `/api/v1/models`, Ollama's `/api/ps`, llama.cpp's `/props`. Data, not code — the\n * request shape is the same for everyone and only the probe differs. With it,\n * `fetchModels` makes one extra request at the endpoint's origin (the `/v1` path\n * stripped) and prefers the EFFECTIVE window (what the server loaded, which is where\n * it truncates) over the trained maximum. A probe that fails leaves the models\n * without a window; it never fails the model list. `'vllm'` needs no extra request,\n * its `/v1/models` already carries `max_model_len`, and is accepted so a preset can\n * say what it is.\n */\n server?: 'lmstudio' | 'ollama' | 'llamacpp' | 'vllm';\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 /**\n * Fields added to every chat request body, for a vendor switch outside the OpenAI\n * schema, e.g. Zhipu's `thinking: { type: 'disabled' }` on GLM. The body's\n * counterpart of `extraHeaders`. The fields this adapter writes stay its own\n * (`model`, `messages`, `stream`, `tools`… see `ADAPTER_FIELDS`): an\n * `extraBody` key of the same name is ignored.\n */\n extraBody?: Record<string, unknown>;\n /** Override the default apiKey+endpoint settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n/** The body fields `buildRequest` writes itself: `extraBody` cannot replace them. */\nconst ADAPTER_FIELDS = new Set(['model', 'messages', 'temperature', 'max_tokens', 'stream', 'stream_options', 'seed', 'tools', 'tool_choice']);\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 // Two arms, exhaustive: a part is text or an image. A third used to answer the\n // removed `AparteFilePart` with an empty text part — a branch nothing could reach,\n // standing in for inline-file support this format does not have.\n return content.map(p => (p.type === 'text'\n ? { type: 'text', text: p.text }\n : { type: 'image_url', image_url: { url: p.image } }));\n}\n\n/**\n * The two roles a tool turn used to wear. `AparteChatMessage` no longer allows either, so\n * the comparison is on the role as a string: a message carrying one comes from code built\n * against an older aparté, which no type here can catch. Warned once per role, then\n * skipped — passing it on would put an unknown role on the wire and 400 the whole request.\n */\nconst LEGACY_TOOL_ROLES = new Set(['tool_call', 'tool_result']);\nconst warnedLegacyRoles = new Set<string>();\nfunction warnLegacyToolRole(role: string): void {\n if (warnedLegacyRoles.has(role)) return;\n warnedLegacyRoles.add(role);\n console.warn(\n `[@aparte/provider-openai-compat] Skipping a message with the removed role \"${role}\".`\n + \" A tool turn is now an assistant message carrying its calls — { role: 'assistant',\"\n + \" content: <what it said>, toolCalls } — followed by one { role: 'tool', content:\"\n + ' <result>, toolCallId, toolName } per call. This warning is removed in 0.18.',\n );\n}\n\n// The other half of that legacy shape, and it is deliberately NOT warned: an assistant\n// message built by hand with both `toolCalls` and image parts loses the images here.\n// Chat Completions does not carry them — an assistant message's content parts are `text`\n// and `refusal`, never `image_url` — so the mapping that used to emit them was a 400\n// waiting to happen, and the loop cannot mint the shape (`stream-run.ts` builds an\n// assistant's content from a string). Images on a USER turn are carried, unchanged.\n\n/** AparteChatMessage[] → OpenAI messages (incl. an assistant turn's calls and the `tool` answers). */\nfunction toOpenAIMessages(messages: AparteChatMessage[]): unknown[] {\n const out: unknown[] = [];\n for (const msg of messages) {\n const role: string = msg.role;\n if (LEGACY_TOOL_ROLES.has(role)) {\n warnLegacyToolRole(role);\n continue;\n }\n if (msg.role === 'tool') {\n out.push({ role: 'tool', tool_call_id: msg.toolCallId, content: contentToText(msg.content) });\n continue;\n }\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\n out.push({\n role: 'assistant',\n // `||`, not `??`: this API takes null (or nothing) beside `tool_calls`, and\n // an assistant that said nothing before its calls now carries '' — which\n // some endpoints reject and the rest read as an empty reply.\n content: contentToText(msg.content) || 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 continue;\n }\n out.push({ role: msg.role, content: toOpenAIContent(msg.content) });\n }\n return out;\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\n/** Map the server's `finish_reason` onto aparté's closed union. */\nfunction toFinishReason(raw: string): AparteFinishReason {\n switch (raw) {\n case 'stop': return 'stop';\n case 'length': return 'length';\n case 'tool_calls':\n case 'function_call': return 'tool-calls';\n case 'content_filter': return 'content-filter';\n default: return 'other';\n }\n}\n\n/** The message of a `{ \"error\": … }` payload: `error.message`, or the error itself when it is a string. */\nfunction errorPayloadMessage(error: unknown): string {\n if (typeof error === 'string' && error) return error;\n const message = (error as { message?: unknown } | null)?.message;\n return typeof message === 'string' && message ? message : 'The server reported an error';\n}\n\n/**\n * What servers write when the prompt is longer than the model's window: OpenAI's\n * \"maximum context length\", the generic \"context length\", LM Studio's \"Context size has\n * been exceeded\", llama.cpp's `exceed_context_size_error` (\"the request exceeds the\n * available context size\"), Anthropic-compatible gateways' \"prompt is too long\". Matched\n * against the message, the type and the code of the payload, because each server puts the\n * telltale in a different one.\n */\nconst CONTEXT_OVERFLOW = /maximum context length|context length|context size has been exceeded|exceed_context_size_error|exceeds the available context size|prompt is too long/i;\n\n/** Is this `{ \"error\": … }` payload a prompt that did not fit? OpenAI's own `context_length_exceeded` code says so outright. */\nfunction isContextOverflow(error: unknown): boolean {\n if (typeof error === 'string') return CONTEXT_OVERFLOW.test(error);\n const fields = error as { message?: unknown; type?: unknown; code?: unknown } | null;\n const strings = [fields?.message, fields?.type, fields?.code].filter((v): v is string => typeof v === 'string');\n return strings.some((v) => v === 'context_length_exceeded' || CONTEXT_OVERFLOW.test(v));\n}\n\n/**\n * The error event for a payload the server streamed after its 200. An overflow carries\n * its code, so the client can compact and resend instead of showing the prose.\n */\nfunction errorEvent(error: unknown): AparteStreamEvent {\n const message = errorPayloadMessage(error);\n return isContextOverflow(error)\n ? { type: 'error', message, code: AparteErrorCode.USAGE_CONTEXT_EXCEEDED }\n : { type: 'error', message };\n}\n\n// ─── Context window ──────────────────────────────────────────────────────────\n\n/**\n * The fields a `GET /models` entry may carry its window in, by vendor: OpenRouter and\n * Together (`context_length`), vLLM (`max_model_len`), Groq (`context_window`), Mistral\n * and LM Studio (`max_context_length`). Read in this order; the first positive integer wins.\n */\nconst WINDOW_FIELDS = ['context_length', 'max_model_len', 'context_window', 'max_context_length'] as const;\n\nfunction positiveInt(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : undefined;\n}\n\nfunction genericWindow(entry: Record<string, unknown>): number | undefined {\n for (const field of WINDOW_FIELDS) {\n const n = positiveInt(entry[field]);\n if (n !== undefined) return n;\n }\n return undefined;\n}\n\ntype WindowOf = (modelId: string) => number | undefined;\n\ntype Json = Record<string, unknown>;\n\n/** A server's JSON is whatever it sent: narrow to an object, or to nothing. */\nfunction asJson(v: unknown): Json {\n return v !== null && typeof v === 'object' ? v as Json : {};\n}\n\nfunction asJsonList(v: unknown): Json[] {\n return Array.isArray(v) ? v.map(asJson) : [];\n}\n\nasync function getJSON(url: string, headers: Record<string, string>): Promise<Json> {\n const response = await fetch(url, { headers });\n if (!response.ok) throw new Error(`HTTP ${response.status}`);\n return asJson(await response.json());\n}\n\n/**\n * Ask a local server for the window it serves, through its own API. Returns a lookup by\n * model id. Throws on a failed probe; the caller turns that into \"no window\".\n *\n * What each one reads, and what it deliberately does NOT:\n * - LM Studio: a LOADED model's `loaded_instances[].config.context_length`. An unloaded\n * model has only `max_context_length`, the trained maximum, which is not what a JIT\n * load will run with — reading it would draw a gauge against a ceiling the server\n * never enforces, so an unloaded model is left without a window.\n * - Ollama: `/api/ps`, a loaded runner's `context_length`. Ollama also truncates the\n * prompt silently, and `/api/show` only has `num_ctx` when a Modelfile bakes one in,\n * so the trained maximum there is never used either.\n * - llama.cpp: `default_generation_settings.n_ctx` of `/props`, the per-slot window,\n * one value for the one model the process serves.\n */\nasync function probeWindows(\n server: NonNullable<OpenAICompatProviderOptions['server']>,\n origin: string,\n headers: Record<string, string>,\n): Promise<WindowOf | undefined> {\n if (server === 'lmstudio') {\n const byId = new Map<string, number>();\n let data: Json | undefined;\n try {\n data = await getJSON(`${origin}/api/v1/models`, headers);\n } catch {\n // Before the v1 REST API (and still beside it), `/api/v0/models` lists each\n // model with the window it was loaded at: `loaded_context_length` on a\n // `state: \"loaded\"` entry, read live on LM Studio 0.4.26.\n const legacy = await getJSON(`${origin}/api/v0/models`, headers);\n for (const m of asJsonList(legacy['data'])) {\n const n = positiveInt(m['loaded_context_length']);\n if (typeof m['id'] === 'string' && n !== undefined) byId.set(m['id'], n);\n }\n return id => byId.get(id);\n }\n for (const m of asJsonList(data['models'])) {\n const instances = asJsonList(m['loaded_instances']);\n // A model loaded under an identifier of its own (`lms load … --identifier`) is\n // listed by `/v1/models` under that identifier, which is the instance's id —\n // so each instance answers for its own id, and the model's key for its first.\n for (const instance of instances) {\n const n = positiveInt(asJson(instance['config'])['context_length']);\n if (typeof instance['id'] === 'string' && n !== undefined) byId.set(instance['id'], n);\n }\n const first = positiveInt(asJson(instances[0]?.['config'])['context_length']);\n if (typeof m['key'] === 'string' && first !== undefined && !byId.has(m['key'])) byId.set(m['key'], first);\n }\n return id => byId.get(id);\n }\n if (server === 'ollama') {\n const data = await getJSON(`${origin}/api/ps`, headers);\n const byName = new Map<string, number>();\n for (const m of asJsonList(data['models'])) {\n const n = positiveInt(m['context_length']);\n if (n === undefined) continue;\n for (const name of [m['name'], m['model']]) if (typeof name === 'string') byName.set(name, n);\n }\n // `/v1/models` lists `llama3.2:latest`; a bare `llama3.2` is the same model.\n return id => byName.get(id) ?? byName.get(id.endsWith(':latest') ? id.slice(0, -7) : `${id}:latest`);\n }\n if (server === 'llamacpp') {\n const data = await getJSON(`${origin}/props`, headers);\n const n = positiveInt(asJson(data['default_generation_settings'])['n_ctx']);\n return n === undefined ? undefined : () => n;\n }\n return undefined;\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` / `classifyError` optional because some vendors authenticate\n * by query string, never answer non-streaming, or say nothing a client could classify.\n * 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' | 'classifyError'>>;\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 * aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));\n * // or any compat endpoint, no preset needed:\n * aparteGlobalConfig.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 (opts.listsModels === false) return opts.models ?? [];\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<Record<string, unknown> & { id: string; name?: string }> };\n\n // The server's own view of the window, when this endpoint says which\n // server it is. Quiet on failure: the list is still good without it.\n let probed: WindowOf | undefined;\n if (opts.server) {\n try {\n probed = await probeWindows(opts.server, new URL(endpoint).origin, headers);\n } catch (error) {\n console.warn(`[${displayName}] Could not read the context window from the ${opts.server} server:`, error);\n }\n }\n // A window the consumer declared on a static model beats anything read.\n const declared = new Map((opts.models ?? []).flatMap(m => (m.contextWindow ? [[m.id, m.contextWindow] as const] : [])));\n\n return (data.data ?? []).map(m => {\n const contextWindow = declared.get(m.id) ?? probed?.(m.id) ?? genericWindow(m);\n return {\n id: m.id,\n name: m.name || m.id,\n ...(contextWindow !== undefined ? { contextWindow } : {}),\n // `function_calling` is declared because it is a property of the\n // WIRE FORMAT, which is what this provider is: `/chat/completions`\n // takes a `tools` array, and every server behind a compat endpoint\n // accepts one. `/models` returns `{id, object, owned_by}` and says\n // nothing about tools, so waiting for it to declare the capability\n // means never declaring it — and core gates tools on exactly this\n // field, so a registered tool, an approval gate and the whole\n // elicitation path were dead on this provider: the model was asked\n // to use a tool it had never been sent.\n //\n // The failure mode of over-declaring is mild and visible: a model\n // that cannot call tools simply does not call one. The failure mode\n // of under-declaring was silent and total. A server that rejects a\n // `tools` array outright surfaces as an error from its own endpoint,\n // which is the right place for that argument to happen.\n capabilities: ['streaming', 'function_calling'],\n };\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 (AparteDirectTransport / AparteBackendTransport).\n defaultEndpoint: opts.baseURL,\n\n buildRequest(request: AparteChatRequest) {\n const body: Record<string, unknown> = {\n ...Object.fromEntries(Object.entries(opts.extraBody ?? {}).filter(([key]) => !ADAPTER_FIELDS.has(key))),\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 /*\n * A forced tool travels; `'auto'` is only the DEFAULT. It used to be\n * written unconditionally, so `toolChoice: { name }` — a shape the loop\n * passes straight through, since it intercepts only `{ name, input }`\n * (the synthetic call it runs itself) — was silently downgraded to\n * \"the model decides\", while the ai-sdk bridge honoured the same field.\n * `'none'` never reaches here: core strips the tools upstream.\n */\n const forced = typeof request.toolChoice === 'object' ? request.toolChoice.name : undefined;\n body['tool_choice'] = forced ? { type: 'function', function: { name: forced } } : '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 /**\n * A 400 (or a 413) whose body says the prompt did not fit is an overflow, not a bad\n * request: the client compacts and sends the turn again. Other statuses keep what\n * the status says.\n */\n classifyError(status: number, body: unknown): AparteErrorCode | undefined {\n if (status !== 400 && status !== 413) return undefined;\n return isContextOverflow((body as { error?: unknown } | null | undefined)?.error)\n ? AparteErrorCode.USAGE_CONTEXT_EXCEEDED\n : undefined;\n },\n\n parseText(json: unknown): string {\n // A 200 that carries `error` and no `choices` is a failure, not an empty\n // reply: LM Studio answers a context overflow this way. The direct\n // transport propagates the throw as the turn's error.\n const payload = json as { error?: unknown; choices?: unknown } | null;\n if (payload?.error && !payload.choices) {\n const failure = new Error(errorPayloadMessage(payload.error));\n throw isContextOverflow(payload.error)\n ? Object.assign(failure, { code: AparteErrorCode.USAGE_CONTEXT_EXCEEDED })\n : failure;\n }\n return (json as { choices?: Array<{ message?: { content?: string } }> })?.choices?.[0]?.message?.content || '';\n },\n\n /** What a non-streamed answer cites: the same annotations and Perplexity fields a stream carries. */\n parseSources(json: unknown) {\n const payload = json as { choices?: Array<{ message?: { content?: unknown; annotations?: unknown } }>; citations?: unknown; search_results?: unknown } | null;\n const message = payload?.choices?.[0]?.message;\n const cited = createSourceCollector();\n cited.addAnnotations(message?.annotations);\n cited.addListed(payload?.citations, payload?.search_results);\n const event = cited.finish(typeof message?.content === 'string' ? message.content : '');\n return event ? { sources: event.sources, ...(event.citations ? { citations: event.citations } : {}) } : undefined;\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 * - `annotations` (url_citation) and Perplexity's `citations` / `search_results`\n * → ONE `sources` event, just before `done` (see `sources.ts`)\n * - `finish_reason` → `finishReason` on the `done`\n * - `{ error }` data line → error event, and the stream ends there (no `done`)\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 /*\n * Tool call accumulation state, in wire order, keyed by `toolCallKey` below.\n * A Map, not an object: the key comes off the wire, and on a plain `{}` a chunk\n * whose `index` is `\"__proto__\"` writes into Object.prototype for the whole page.\n */\n const toolCalls = new Map<string, { id: string; name: string; args: string }>();\n /** The call the last delta touched — what a bare argument fragment continues. */\n let lastToolKey: string | null = null;\n /**\n * Every key a delta has addressed a call by → that call's slot in `toolCalls`.\n * One call can be addressed both ways in one turn (by `index` in a chunk, by `id`\n * in the next), so the identity is the alias, not the key of the moment.\n */\n const toolCallAliases = new Map<string, string>();\n /** Ids minted for a vendor that sends none. Per stream, i.e. per turn. */\n let mintedCallIds = 0;\n let capturedUsage: AparteUsage | undefined;\n /** The last non-null `finish_reason` seen, mapped; every `done` carries it. */\n let finishReason: AparteFinishReason | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n /**\n * What the call has said so far, and what it cites. A citation's offsets are into the\n * whole message text, so the `sources` event waits for the end of the stream, when the\n * text is complete and the offsets can be checked against it.\n */\n let streamed = '';\n const cited = createSourceCollector();\n // Once, at the first of a tool call and the end: a call the loop refuses, or one with no\n // handler, ends the turn without reading on, and its sources would never arrive.\n let sourcesSent = false;\n const flushSources = (controller: ReadableStreamDefaultController<AparteStreamEvent>): void => {\n if (sourcesSent) return;\n sourcesSent = true;\n const event = cited.finish(streamed);\n if (event) controller.enqueue(event);\n };\n\n /*\n * Emit the accumulated calls and clear the map.\n *\n * Called at EVERY graceful end of a turn, not only on `finish_reason: 'tool_calls'`:\n * the accumulation spans the whole stream, and a vendor that ends with `stop`,\n * `length` or a bare `[DONE]` after streaming tool-call deltas used to drop them —\n * the model had asked for a tool and the loop never heard. `complete` says the\n * model declared the calls finished: a parse failure there is malformed JSON (a\n * real small-model failure) and the tool still runs, on `{}`. Elsewhere it is\n * truncation, and running a tool on `{}` for a call the model never finished would\n * be worse than dropping it — so those are dropped, with a breadcrumb.\n */\n const flushToolCalls = (controller: ReadableStreamDefaultController<AparteStreamEvent>, complete: boolean): void => {\n for (const entry of toolCalls.values()) {\n let input: Record<string, unknown> = {};\n try {\n input = entry.args.trim() ? JSON.parse(entry.args) : {};\n } catch {\n if (!complete) {\n console.warn(`[openai-compat] Tool \"${entry.name}\" was cut mid-arguments (the turn ended without finish_reason 'tool_calls'); the call is dropped. Raw:`, entry.args);\n continue;\n }\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 toolCalls.clear();\n toolCallAliases.clear();\n lastToolKey = null;\n };\n\n /*\n * Which accumulated call a `delta.tool_calls` entry belongs to.\n *\n * Both `index` and `id` address a call, and a vendor may use one in a chunk and\n * the other in the next — so neither can BE the identity: a delta's keys are\n * ALIASES that all resolve to the same slot. A key already resolved names the\n * call, and every key a delta carries is recorded against the slot it lands on.\n * Picking one key instead (`index` first, `id` next) split such a call in two,\n * the second half nameless, and the turn ran the tool on `{}`.\n *\n * When no key of the delta is known yet, the function NAME decides — it appears\n * only on a call's FIRST delta, so a nameless delta continues the call before it\n * and a named one opens its own. That single rule covers the vendor whose deltas\n * never carry both addresses at once (open by `id`, continue by `index`, or the\n * reverse) and the vendor that carries neither: `index` is a streaming\n * convenience, not part of the function-call payload, and keying on it alone\n * (`Number(tc.index ?? 0)`) put every call of a server that omits it on slot 0.\n *\n * `null` means \"skip this entry\": an `index` the vendor sent that is not a\n * non-negative integer is not a slot number, it is malformed (or hostile —\n * `\"__proto__\"` was the shape that made this a Map).\n */\n const toolCallKey = (tc: { index?: unknown; id?: string; function?: { name?: string } }): string | null => {\n let indexKey: string | undefined;\n if (tc.index !== undefined && tc.index !== null) {\n // Made a number, not annotated as one: `index` is whatever the vendor's JSON put there.\n const idx = Number(tc.index);\n if (!Number.isInteger(idx) || idx < 0) return null;\n indexKey = `i${idx}`;\n }\n const idKey = tc.id ? `d${tc.id}` : undefined;\n const known = (idKey ? toolCallAliases.get(idKey) : undefined) ?? (indexKey ? toolCallAliases.get(indexKey) : undefined);\n // No key of this delta is known yet. A nameless delta never OPENS a call, so it\n // continues the call before it — which is what keeps ONE call together when the\n // vendor opens it with one address and continues it with the other, neither\n // delta carrying both. A named delta still opens its own slot.\n const key = known ?? ((!tc.function?.name && lastToolKey !== null) ? lastToolKey : (idKey ?? indexKey));\n if (key === undefined) return `p${toolCalls.size}`;\n if (idKey) toolCallAliases.set(idKey, key);\n if (indexKey) toolCallAliases.set(indexKey, key);\n return key;\n };\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 // A turn that streamed tool-call deltas and ended on `stop`,\n // `length` or a bare [DONE]: the calls are still the model's.\n flushToolCalls(controller, false);\n flushSources(controller);\n controller.enqueue({ type: 'done', usage: capturedUsage, ...(finishReason ? { finishReason } : {}) });\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 // Perplexity's list of sources rides on any chunk, usage-only ones\n // included, which is why it is read before the choice is.\n cited.addListed(json.citations, json.search_results);\n\n // An error the server streamed AFTER a 200 (LM Studio on a context\n // overflow, OpenAI and vLLM mid-stream). Without this the line was\n // skipped as \"no choices\" and the turn ended on an empty `done`:\n // an empty reply marked completed. An `event: error` line before it\n // is ignored like every non-`data:` line; this data line is the payload.\n if (json.error) {\n controller.enqueue(errorEvent(json.error));\n reader?.cancel().catch(() => undefined);\n return;\n }\n\n const choice = json.choices?.[0];\n if (!choice) continue;\n if (typeof choice.finish_reason === 'string' && choice.finish_reason) {\n finishReason = toFinishReason(choice.finish_reason);\n }\n\n const delta = choice.delta;\n // Annotations: on the delta as OpenAI and OpenRouter stream them\n // (usually in a late chunk, after the text), or on a whole `message`\n // as some servers stream the non-streamed shape.\n cited.addAnnotations(delta?.annotations);\n cited.addAnnotations(choice.message?.annotations);\n if (delta) {\n if (delta.reasoning_content) {\n controller.enqueue({ type: 'thinking', delta: delta.reasoning_content });\n }\n if (delta.content) {\n streamed += 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 key = toolCallKey(tc);\n if (key === null) continue;\n let entry = toolCalls.get(key);\n if (!entry) {\n /*\n * An id is MINTED when the vendor sends none. `id: ''`\n * used to travel: the transcript keys a row on\n * `tool-${id}` and the history a result on\n * `toolCallId`, so two id-less calls in one turn shared\n * one row (the second call's result landed on the first\n * call's line) and one history slot an OpenAI-shaped\n * endpoint rejects on the next turn. A real id arriving\n * in a later chunk still wins, below. The mint is\n * namespaced because `call_1` is what an OpenAI-shaped\n * server calls its own first call: a turn mixing an\n * id-less call with a real `call_1` minted a duplicate.\n */\n entry = { id: tc.id ?? `aparte-call-${++mintedCallIds}`, name: tc.function?.name ?? '', args: '' };\n toolCalls.set(key, entry);\n }\n lastToolKey = key;\n if (tc.id) entry.id = tc.id;\n if (tc.function?.name) entry.name = tc.function.name;\n if (tc.function?.arguments) entry.args += tc.function.arguments;\n }\n }\n }\n\n // The model declared the calls complete: emit them now (and clear\n // the map, so a server that repeats the finish chunk cannot\n // re-emit them). Do NOT emit `done` or return here: under\n // `include_usage` (which buildRequest requests) the usage-only\n // chunk arrives AFTER this finish chunk, so stopping now dropped\n // usage on every tool-call turn — i.e. most turns of an agent.\n // Keep reading; the single `done` comes from `[DONE]` below, or\n // from the end-of-stream fallback if the socket just closes.\n if (choice.finish_reason === 'tool_calls') {\n flushSources(controller);\n flushToolCalls(controller, true);\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 // The socket closed without [DONE]: same rule, the calls are flushed first.\n flushToolCalls(controller, false);\n flushSources(controller);\n controller.enqueue({ type: 'done', usage: capturedUsage, ...(finishReason ? { finishReason } : {}) });\n } catch (err: unknown) {\n // AbortError surfaces here when the caller's signal fires mid-stream:\n // the consumer (agent loop) cancelled on purpose — end quietly, the\n // same way the ai-sdk provider does. Reporting it as an `error` event\n // makes a deliberate Stop indistinguishable from a network failure,\n // and the loop's error branch then replaces the streamed answer with\n // an error bubble.\n if ((err as { name?: string } | undefined)?.name !== 'AbortError') {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n }\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/sources.ts","../src/presets.ts","../src/index.ts"],"sourcesContent":["/**\n * sources.ts — the sources a reply cites, read off the two shapes a compat endpoint sends.\n *\n * OpenAI and OpenRouter attach `url_citation` annotations to the message, with the\n * offsets of the passage they support. Perplexity attaches no offsets: it sends the list\n * of sources at the root of the chunk and writes `[1]`, `[2]` into the text itself.\n * Both end in the same `sources` event, emitted once before `done`, because the offsets\n * are into the text the whole call streamed and only mean something once it is complete.\n *\n * What is read here is passed through as data. A `javascript:` URL in an annotation\n * leaves as it arrived: refusing it is the renderer's job (the sanitizer's URL policy),\n * and a provider that filtered would be a second, silent policy.\n */\n\nimport type { AparteSource, AparteCitation } from '@aparte/core';\n\n/** The event {@link createSourceCollector} builds: what `AparteStreamEventMap['sources']` carries. */\nexport interface SourcesEvent {\n type: 'sources';\n sources: AparteSource[];\n citations?: AparteCitation[];\n}\n\n/** A non-empty string, or nothing: the wire sends `null` and `''` for what it does not have. */\nconst str = (value: unknown): string | undefined => (typeof value === 'string' && value ? value : undefined);\n\n/** `[n]` as Perplexity writes it in the text: 1-based into its list of sources. */\nconst MARKER = /\\[(\\d{1,3})\\]/g;\n\n/**\n * Where `text` holds code, as CommonMark reads it: a fence runs to a closing fence of its\n * character at least as long (or to the end), an indented line after a blank one is code,\n * and a backquote span closes on the same run within its paragraph — `\\`` opens nothing.\n */\nfunction codeRanges(text: string): Array<[number, number]> {\n const ranges: Array<[number, number]> = [];\n let at = 0;\n let fence: { char: string; length: number; start: number } | null = null;\n let indented = -1;\n let blank = true;\n for (const line of text.split('\\n')) {\n const next = at + line.length + 1;\n if (fence) {\n const close = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/.exec(line);\n if (close && close[1]![0] === fence.char && close[1]!.length >= fence.length) {\n ranges.push([fence.start, next]);\n fence = null;\n }\n } else {\n const open = /^ {0,3}(`{3,}|~{3,})/.exec(line);\n const code = /^(?: {4}|\\t)/.test(line) && line.trim() !== '' && (blank || indented >= 0);\n if ((open || !code) && indented >= 0 && line.trim() !== '') {\n ranges.push([indented, at]);\n indented = -1;\n }\n if (open) fence = { char: open[1]![0]!, length: open[1]!.length, start: at };\n else if (code && indented < 0) indented = at;\n }\n blank = line.trim() === '';\n at = next;\n }\n if (fence) ranges.push([fence.start, text.length]);\n if (indented >= 0) ranges.push([indented, text.length]);\n const inside = (i: number): boolean => ranges.some(([from, to]) => i >= from && i < to);\n for (const m of text.matchAll(/(?<![\\\\`])(`+)(?!`)((?:(?!\\n[ \\t]*\\n)[\\s\\S])*?[^`])\\1(?!`)/g)) {\n const i = m.index ?? 0;\n if (!inside(i)) ranges.push([i, i + m[0].length]);\n }\n return ranges;\n}\n\n/**\n * Collects annotations and Perplexity's root fields as chunks arrive and builds the\n * `sources` event once the text is complete. One collector per stream, i.e. per call.\n */\nexport function createSourceCollector() {\n const sources: AparteSource[] = [];\n /** De-duplicated by URL: the same page cited for two passages is one source. */\n const byUrl = new Map<string, AparteSource>();\n /** Annotated passages as the wire gave them, clamped when the text length is known. */\n const annotated: Array<{ start: unknown; end: unknown; id: string }> = [];\n /** Perplexity's fields: the LAST chunk that carries one wins, whatever came before. */\n let urlList: unknown;\n let searchResults: unknown;\n\n /** The id of the source for this URL/title, minting one the first time. `undefined` when there is nothing to show. */\n const register = (url: string | undefined, title: string | undefined, snippet: string | undefined): string | undefined => {\n if (url === undefined && title === undefined) return undefined;\n const known = url !== undefined ? byUrl.get(url) : undefined;\n if (known) {\n // A later mention can carry what the first one lacked.\n known.title ??= title;\n known.snippet ??= snippet;\n return known.id;\n }\n const source: AparteSource = {\n id: `s${sources.length + 1}`,\n ...(url !== undefined ? { url } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(snippet !== undefined ? { snippet } : {}),\n };\n sources.push(source);\n if (url !== undefined) byUrl.set(url, source);\n return source.id;\n };\n\n return {\n /**\n * `delta.annotations` / `message.annotations`: `{ type: 'url_citation', url_citation:\n * { start_index, end_index, url, title, content? } }`. An entry of another kind\n * (`file_citation`, …) carries no `url_citation` and is skipped.\n */\n addAnnotations(list: unknown): void {\n if (!Array.isArray(list)) return;\n for (const entry of list) {\n const cited = (entry as { url_citation?: unknown } | null)?.url_citation;\n if (!cited || typeof cited !== 'object') continue;\n const c = cited as Record<string, unknown>;\n const id = register(str(c['url']), str(c['title']), str(c['content']));\n if (id !== undefined) annotated.push({ start: c['start_index'], end: c['end_index'], id });\n }\n },\n\n /** The root-level `citations: string[]` and `search_results: [{ title, url, snippet? }]`. */\n addListed(citations: unknown, results: unknown): void {\n if (Array.isArray(citations) && citations.length) urlList = citations;\n if (Array.isArray(results) && results.length) searchResults = results;\n },\n\n /**\n * The `sources` event for a call whose text is `text`, or `undefined` when nothing\n * was cited — a reply with no annotations and no list leaves the stream untouched.\n */\n finish(text: string): SourcesEvent | undefined {\n const citations: AparteCitation[] = [];\n\n // An offset the wire got wrong (past the end, negative, not a number) is\n // brought back into the text, or the passage is dropped: it is still a source.\n const within = (value: unknown): number | undefined => (\n typeof value === 'number' && Number.isFinite(value) ? Math.min(Math.max(Math.trunc(value), 0), text.length) : undefined\n );\n for (const { start: from, end: to, id } of annotated) {\n const end = within(to);\n // An end at the very start (or before it, brought back) is no passage.\n if (end === undefined || end <= 0) continue;\n const s = within(from);\n const start = s !== undefined && s < end ? s : undefined;\n // Two annotations on one passage are one citation with two sources (\"+1\").\n const same = citations.find(c => c.start === start && c.end === end);\n if (same) {\n if (!same.sources.includes(id)) same.sources.push(id);\n } else {\n citations.push({ ...(start !== undefined ? { start } : {}), end, sources: [id] });\n }\n }\n\n // Perplexity: the richer `search_results` when it sent them, else the bare URLs.\n const results = Array.isArray(searchResults) ? searchResults : [];\n const listed: Array<string | undefined> = results.length\n ? results.map(r => register(\n str((r as { url?: unknown } | null)?.url),\n str((r as { title?: unknown } | null)?.title),\n str((r as { snippet?: unknown } | null)?.snippet),\n ))\n : (Array.isArray(urlList) ? urlList : []).map(u => register(str(u), undefined, undefined));\n if (listed.length) {\n // Code is not prose: an `arr[1]` in a code span or a fence is no marker.\n const code = codeRanges(text);\n for (const m of text.matchAll(MARKER)) {\n const id = listed[Number(m[1]) - 1];\n if (id === undefined) continue;\n const start = m.index ?? 0;\n if (code.some(([a, b]) => start >= a && start < b)) continue;\n citations.push({ start, end: start + m[0].length, sources: [id], marker: true });\n }\n }\n\n if (!sources.length) return undefined;\n return { type: 'sources', sources, ...(citations.length ? { citations } : {}) };\n },\n };\n}\n","/**\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 * aparteGlobalConfig.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\n// Perplexity's Sonar models answer from a web search and cite what they read: the sources\n// arrive as `citations` / `search_results` on the chunk and the text carries `[1]`, `[2]`\n// markers, which this adapter turns into a `sources` event. There is no `GET /models` to\n// ask, so the list is declared here and both `getModels()` and `fetchModels()` serve it.\n// The icon is generic line art (a magnifier), not their mark.\nconst PERPLEXITY: OpenAICompatProviderOptions = {\n id: 'perplexity',\n baseURL: 'https://api.perplexity.ai',\n name: 'Perplexity',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"M21 21l-4.3-4.3\"/></svg>`,\n color: '#20808d',\n description: 'Sonar models that search the web and cite their sources',\n helpUrl: 'https://www.perplexity.ai/settings/api',\n listsModels: false,\n models: [\n { id: 'sonar', name: 'Sonar', capabilities: ['streaming'] },\n { id: 'sonar-pro', name: 'Sonar Pro', capabilities: ['streaming'] },\n { id: 'sonar-reasoning-pro', name: 'Sonar Reasoning Pro', capabilities: ['streaming'] },\n { id: 'sonar-deep-research', name: 'Sonar Deep Research', capabilities: ['streaming'] },\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 server: 'lmstudio',\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 server: 'ollama',\n helpUrl: 'https://ollama.com/',\n};\n\n// llama.cpp's `llama-server` and vLLM both serve `/v1/chat/completions` on their own\n// port. The icons are generic line art (a chip, a stack of layers), not either project's mark.\nconst LLAMACPP: OpenAICompatProviderOptions = {\n id: 'llamacpp',\n baseURL: 'http://localhost:8080/v1',\n name: 'llama.cpp',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\"/><path d=\"M7 9h2M7 12h6M7 15h4\"/></svg>`,\n color: '#64748b',\n description: 'Run GGUF models locally with llama-server',\n hasFreeModels: true,\n isLocal: true,\n server: 'llamacpp',\n helpUrl: 'https://github.com/ggml-org/llama.cpp',\n};\n\nconst VLLM: OpenAICompatProviderOptions = {\n id: 'vllm',\n baseURL: 'http://localhost:8000/v1',\n name: 'vLLM',\n icon: `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M12 3l9 5-9 5-9-5 9-5z\"/><path d=\"M3 13l9 5 9-5\"/></svg>`,\n color: '#6b7280',\n description: 'Serve open models from your own GPU with vLLM',\n hasFreeModels: true,\n isLocal: true,\n server: 'vllm',\n helpUrl: 'https://docs.vllm.ai/',\n};\n\n/** Vendor presets for {@link createOpenAICompatProvider}. Pure data. */\nexport const presets = { OPENAI, MISTRAL, ZAI, OPENROUTER, PERPLEXITY, LMSTUDIO, OLLAMA, LLAMACPP, VLLM } 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 AparteFinishReason,\n AparteFormatAdapter,\n} from '@aparte/core';\nimport { AparteErrorCode, contentToText } from '@aparte/core';\nimport { createSourceCollector } from './sources.js';\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 /**\n * `false` when the endpoint answers no `GET /models` (Perplexity): `fetchModels` then\n * serves `models` instead of asking, rather than logging a failed request.\n */\n listsModels?: boolean;\n /**\n * Which local server this endpoint is, so `fetchModels` can read the context window\n * the server actually serves through that server's own API: LM Studio's\n * `/api/v1/models`, Ollama's `/api/ps`, llama.cpp's `/props`. Data, not code — the\n * request shape is the same for everyone and only the probe differs. With it,\n * `fetchModels` makes one extra request at the endpoint's origin (the `/v1` path\n * stripped) and prefers the EFFECTIVE window (what the server loaded, which is where\n * it truncates) over the trained maximum. A probe that fails leaves the models\n * without a window; it never fails the model list. `'vllm'` needs no extra request,\n * its `/v1/models` already carries `max_model_len`, and is accepted so a preset can\n * say what it is.\n */\n server?: 'lmstudio' | 'ollama' | 'llamacpp' | 'vllm';\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 /**\n * Fields added to every chat request body, for a vendor switch outside the OpenAI\n * schema, e.g. Zhipu's `thinking: { type: 'disabled' }` on GLM. The body's\n * counterpart of `extraHeaders`. The fields this adapter writes stay its own\n * (`model`, `messages`, `stream`, `tools`… see `ADAPTER_FIELDS`): an\n * `extraBody` key of the same name is ignored.\n */\n extraBody?: Record<string, unknown>;\n /** Override the default apiKey+endpoint settings schema. */\n configSchema?: AparteAIProviderConfigSchema;\n}\n\n/** The body fields `buildRequest` writes itself: `extraBody` cannot replace them. */\nconst ADAPTER_FIELDS = new Set(['model', 'messages', 'temperature', 'max_tokens', 'stream', 'stream_options', 'seed', 'tools', 'tool_choice']);\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 // Two arms, exhaustive: a part is text or an image. A third used to answer the\n // removed `AparteFilePart` with an empty text part — a branch nothing could reach,\n // standing in for inline-file support this format does not have.\n return content.map(p => (p.type === 'text'\n ? { type: 'text', text: p.text }\n : { type: 'image_url', image_url: { url: p.image } }));\n}\n\n/**\n * The two roles a tool turn used to wear. `AparteChatMessage` no longer allows either, so\n * the comparison is on the role as a string: a message carrying one comes from code built\n * against an older aparté, which no type here can catch. Warned once per role, then\n * skipped — passing it on would put an unknown role on the wire and 400 the whole request.\n */\nconst LEGACY_TOOL_ROLES = new Set(['tool_call', 'tool_result']);\nconst warnedLegacyRoles = new Set<string>();\nfunction warnLegacyToolRole(role: string): void {\n if (warnedLegacyRoles.has(role)) return;\n warnedLegacyRoles.add(role);\n console.warn(\n `[@aparte/provider-openai-compat] Skipping a message with the removed role \"${role}\".`\n + \" A tool turn is now an assistant message carrying its calls — { role: 'assistant',\"\n + \" content: <what it said>, toolCalls } — followed by one { role: 'tool', content:\"\n + ' <result>, toolCallId, toolName } per call. This warning is removed in 0.18.',\n );\n}\n\n// The other half of that legacy shape, and it is deliberately NOT warned: an assistant\n// message built by hand with both `toolCalls` and image parts loses the images here.\n// Chat Completions does not carry them — an assistant message's content parts are `text`\n// and `refusal`, never `image_url` — so the mapping that used to emit them was a 400\n// waiting to happen, and the loop cannot mint the shape (`stream-run.ts` builds an\n// assistant's content from a string). Images on a USER turn are carried, unchanged.\n\n/** AparteChatMessage[] → OpenAI messages (incl. an assistant turn's calls and the `tool` answers). */\nfunction toOpenAIMessages(messages: AparteChatMessage[]): unknown[] {\n const out: unknown[] = [];\n for (const msg of messages) {\n const role: string = msg.role;\n if (LEGACY_TOOL_ROLES.has(role)) {\n warnLegacyToolRole(role);\n continue;\n }\n if (msg.role === 'tool') {\n out.push({ role: 'tool', tool_call_id: msg.toolCallId, content: contentToText(msg.content) });\n continue;\n }\n if (msg.role === 'assistant' && msg.toolCalls?.length) {\n out.push({\n role: 'assistant',\n // `||`, not `??`: this API takes null (or nothing) beside `tool_calls`, and\n // an assistant that said nothing before its calls now carries '' — which\n // some endpoints reject and the rest read as an empty reply.\n content: contentToText(msg.content) || 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 continue;\n }\n out.push({ role: msg.role, content: toOpenAIContent(msg.content) });\n }\n return out;\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\n/** Map the server's `finish_reason` onto aparté's closed union. */\nfunction toFinishReason(raw: string): AparteFinishReason {\n switch (raw) {\n case 'stop': return 'stop';\n case 'length': return 'length';\n case 'tool_calls':\n case 'function_call': return 'tool-calls';\n case 'content_filter': return 'content-filter';\n default: return 'other';\n }\n}\n\n/** The message of a `{ \"error\": … }` payload: `error.message`, or the error itself when it is a string. */\nfunction errorPayloadMessage(error: unknown): string {\n if (typeof error === 'string' && error) return error;\n const message = (error as { message?: unknown } | null)?.message;\n return typeof message === 'string' && message ? message : 'The server reported an error';\n}\n\n/**\n * What servers write when the prompt is longer than the model's window: OpenAI's\n * \"maximum context length\", the generic \"context length\", LM Studio's \"Context size has\n * been exceeded\", llama.cpp's `exceed_context_size_error` (\"the request exceeds the\n * available context size\"), Anthropic-compatible gateways' \"prompt is too long\". Matched\n * against the message, the type and the code of the payload, because each server puts the\n * telltale in a different one.\n */\nconst CONTEXT_OVERFLOW = /maximum context length|context length|context size has been exceeded|exceed_context_size_error|exceeds the available context size|prompt is too long/i;\n\n/** Is this `{ \"error\": … }` payload a prompt that did not fit? OpenAI's own `context_length_exceeded` code says so outright. */\nfunction isContextOverflow(error: unknown): boolean {\n if (typeof error === 'string') return CONTEXT_OVERFLOW.test(error);\n const fields = error as { message?: unknown; type?: unknown; code?: unknown } | null;\n const strings = [fields?.message, fields?.type, fields?.code].filter((v): v is string => typeof v === 'string');\n return strings.some((v) => v === 'context_length_exceeded' || CONTEXT_OVERFLOW.test(v));\n}\n\n/**\n * The error event for a payload the server streamed after its 200. An overflow carries\n * its code, so the client can compact and resend instead of showing the prose.\n */\nfunction errorEvent(error: unknown): AparteStreamEvent {\n const message = errorPayloadMessage(error);\n return isContextOverflow(error)\n ? { type: 'error', message, code: AparteErrorCode.USAGE_CONTEXT_EXCEEDED }\n : { type: 'error', message };\n}\n\n// ─── Context window ──────────────────────────────────────────────────────────\n\n/**\n * The fields a `GET /models` entry may carry its window in, by vendor: OpenRouter and\n * Together (`context_length`), vLLM (`max_model_len`), Groq (`context_window`), Mistral\n * and LM Studio (`max_context_length`). Read in this order; the first positive integer wins.\n */\nconst WINDOW_FIELDS = ['context_length', 'max_model_len', 'context_window', 'max_context_length'] as const;\n\nfunction positiveInt(v: unknown): number | undefined {\n return typeof v === 'number' && Number.isInteger(v) && v > 0 ? v : undefined;\n}\n\nfunction genericWindow(entry: Record<string, unknown>): number | undefined {\n for (const field of WINDOW_FIELDS) {\n const n = positiveInt(entry[field]);\n if (n !== undefined) return n;\n }\n return undefined;\n}\n\ntype WindowOf = (modelId: string) => number | undefined;\n\ntype Json = Record<string, unknown>;\n\n/** A server's JSON is whatever it sent: narrow to an object, or to nothing. */\nfunction asJson(v: unknown): Json {\n return v !== null && typeof v === 'object' ? v as Json : {};\n}\n\nfunction asJsonList(v: unknown): Json[] {\n return Array.isArray(v) ? v.map(asJson) : [];\n}\n\n/** The server answered, with an error status: unlike a request that never got an answer. */\nclass HttpError extends Error {}\n\nasync function getJSON(url: string, headers: Record<string, string>): Promise<Json> {\n const response = await fetch(url, { headers });\n if (!response.ok) throw new HttpError(`HTTP ${response.status}`);\n return asJson(await response.json());\n}\n\n/**\n * Ask a local server for the window it serves, through its own API. Returns a lookup by\n * model id. Throws on a failed probe; the caller turns that into \"no window\".\n *\n * What each one reads, and what it deliberately does NOT:\n * - LM Studio: a LOADED model's `loaded_instances[].config.context_length`. An unloaded\n * model has only `max_context_length`, the trained maximum, which is not what a JIT\n * load will run with — reading it would draw a gauge against a ceiling the server\n * never enforces, so an unloaded model is left without a window.\n * - Ollama: `/api/ps`, a loaded runner's `context_length`. Ollama also truncates the\n * prompt silently, and `/api/show` only has `num_ctx` when a Modelfile bakes one in,\n * so the trained maximum there is never used either.\n * - llama.cpp: `default_generation_settings.n_ctx` of `/props`, the per-slot window,\n * one value for the one model the process serves.\n */\nasync function probeWindows(\n server: NonNullable<OpenAICompatProviderOptions['server']>,\n origin: string,\n headers: Record<string, string>,\n): Promise<WindowOf | undefined> {\n if (server === 'lmstudio') {\n const byId = new Map<string, number>();\n let data: Json | undefined;\n try {\n data = await getJSON(`${origin}/api/v1/models`, headers);\n } catch (error) {\n // Only a server that answered without the v1 API has the older one to ask: a\n // request that never got an answer (the server is down, the page is being left)\n // would only send a second one after it.\n if (!(error instanceof HttpError)) throw error;\n // Before the v1 REST API (and still beside it), `/api/v0/models` lists each\n // model with the window it was loaded at: `loaded_context_length` on a\n // `state: \"loaded\"` entry, read live on LM Studio 0.4.26.\n const legacy = await getJSON(`${origin}/api/v0/models`, headers);\n for (const m of asJsonList(legacy['data'])) {\n const n = positiveInt(m['loaded_context_length']);\n if (typeof m['id'] === 'string' && n !== undefined) byId.set(m['id'], n);\n }\n return id => byId.get(id);\n }\n for (const m of asJsonList(data['models'])) {\n const instances = asJsonList(m['loaded_instances']);\n // A model loaded under an identifier of its own (`lms load … --identifier`) is\n // listed by `/v1/models` under that identifier, which is the instance's id —\n // so each instance answers for its own id, and the model's key for its first.\n for (const instance of instances) {\n const n = positiveInt(asJson(instance['config'])['context_length']);\n if (typeof instance['id'] === 'string' && n !== undefined) byId.set(instance['id'], n);\n }\n const first = positiveInt(asJson(instances[0]?.['config'])['context_length']);\n if (typeof m['key'] === 'string' && first !== undefined && !byId.has(m['key'])) byId.set(m['key'], first);\n }\n return id => byId.get(id);\n }\n if (server === 'ollama') {\n const data = await getJSON(`${origin}/api/ps`, headers);\n const byName = new Map<string, number>();\n for (const m of asJsonList(data['models'])) {\n const n = positiveInt(m['context_length']);\n if (n === undefined) continue;\n for (const name of [m['name'], m['model']]) if (typeof name === 'string') byName.set(name, n);\n }\n // `/v1/models` lists `llama3.2:latest`; a bare `llama3.2` is the same model.\n return id => byName.get(id) ?? byName.get(id.endsWith(':latest') ? id.slice(0, -7) : `${id}:latest`);\n }\n if (server === 'llamacpp') {\n const data = await getJSON(`${origin}/props`, headers);\n const n = positiveInt(asJson(data['default_generation_settings'])['n_ctx']);\n return n === undefined ? undefined : () => n;\n }\n return undefined;\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` / `classifyError` optional because some vendors authenticate\n * by query string, never answer non-streaming, or say nothing a client could classify.\n * 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' | 'classifyError'>>;\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 * aparteGlobalConfig.registerAIProvider(createOpenAICompatProvider(presets.OPENROUTER));\n * // or any compat endpoint, no preset needed:\n * aparteGlobalConfig.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 (opts.listsModels === false) return opts.models ?? [];\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<Record<string, unknown> & { id: string; name?: string }> };\n\n // The server's own view of the window, when this endpoint says which\n // server it is. Quiet on failure: the list is still good without it.\n let probed: WindowOf | undefined;\n if (opts.server) {\n try {\n probed = await probeWindows(opts.server, new URL(endpoint).origin, headers);\n } catch (error) {\n console.warn(`[${displayName}] Could not read the context window from the ${opts.server} server:`, error);\n }\n }\n // A window the consumer declared on a static model beats anything read.\n const declared = new Map((opts.models ?? []).flatMap(m => (m.contextWindow ? [[m.id, m.contextWindow] as const] : [])));\n\n return (data.data ?? []).map(m => {\n const contextWindow = declared.get(m.id) ?? probed?.(m.id) ?? genericWindow(m);\n return {\n id: m.id,\n name: m.name || m.id,\n ...(contextWindow !== undefined ? { contextWindow } : {}),\n // `function_calling` is declared because it is a property of the\n // WIRE FORMAT, which is what this provider is: `/chat/completions`\n // takes a `tools` array, and every server behind a compat endpoint\n // accepts one. `/models` returns `{id, object, owned_by}` and says\n // nothing about tools, so waiting for it to declare the capability\n // means never declaring it — and core gates tools on exactly this\n // field, so a registered tool, an approval gate and the whole\n // elicitation path were dead on this provider: the model was asked\n // to use a tool it had never been sent.\n //\n // The failure mode of over-declaring is mild and visible: a model\n // that cannot call tools simply does not call one. The failure mode\n // of under-declaring was silent and total. A server that rejects a\n // `tools` array outright surfaces as an error from its own endpoint,\n // which is the right place for that argument to happen.\n capabilities: ['streaming', 'function_calling'],\n };\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 (AparteDirectTransport / AparteBackendTransport).\n defaultEndpoint: opts.baseURL,\n\n buildRequest(request: AparteChatRequest) {\n const body: Record<string, unknown> = {\n ...Object.fromEntries(Object.entries(opts.extraBody ?? {}).filter(([key]) => !ADAPTER_FIELDS.has(key))),\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 /*\n * A forced tool travels; `'auto'` is only the DEFAULT. It used to be\n * written unconditionally, so `toolChoice: { name }` — a shape the loop\n * passes straight through, since it intercepts only `{ name, input }`\n * (the synthetic call it runs itself) — was silently downgraded to\n * \"the model decides\", while the ai-sdk bridge honoured the same field.\n * `'none'` never reaches here: core strips the tools upstream.\n */\n const forced = typeof request.toolChoice === 'object' ? request.toolChoice.name : undefined;\n body['tool_choice'] = forced ? { type: 'function', function: { name: forced } } : '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 /**\n * A 400 (or a 413) whose body says the prompt did not fit is an overflow, not a bad\n * request: the client compacts and sends the turn again. Other statuses keep what\n * the status says.\n */\n classifyError(status: number, body: unknown): AparteErrorCode | undefined {\n if (status !== 400 && status !== 413) return undefined;\n return isContextOverflow((body as { error?: unknown } | null | undefined)?.error)\n ? AparteErrorCode.USAGE_CONTEXT_EXCEEDED\n : undefined;\n },\n\n parseText(json: unknown): string {\n // A 200 that carries `error` and no `choices` is a failure, not an empty\n // reply: LM Studio answers a context overflow this way. The direct\n // transport propagates the throw as the turn's error.\n const payload = json as { error?: unknown; choices?: unknown } | null;\n if (payload?.error && !payload.choices) {\n const failure = new Error(errorPayloadMessage(payload.error));\n throw isContextOverflow(payload.error)\n ? Object.assign(failure, { code: AparteErrorCode.USAGE_CONTEXT_EXCEEDED })\n : failure;\n }\n return (json as { choices?: Array<{ message?: { content?: string } }> })?.choices?.[0]?.message?.content || '';\n },\n\n /** What a non-streamed answer cites: the same annotations and Perplexity fields a stream carries. */\n parseSources(json: unknown) {\n const payload = json as { choices?: Array<{ message?: { content?: unknown; annotations?: unknown } }>; citations?: unknown; search_results?: unknown } | null;\n const message = payload?.choices?.[0]?.message;\n const cited = createSourceCollector();\n cited.addAnnotations(message?.annotations);\n cited.addListed(payload?.citations, payload?.search_results);\n const event = cited.finish(typeof message?.content === 'string' ? message.content : '');\n return event ? { sources: event.sources, ...(event.citations ? { citations: event.citations } : {}) } : undefined;\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 * - `annotations` (url_citation) and Perplexity's `citations` / `search_results`\n * → ONE `sources` event, just before `done` (see `sources.ts`)\n * - `finish_reason` → `finishReason` on the `done`\n * - `{ error }` data line → error event, and the stream ends there (no `done`)\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 /*\n * Tool call accumulation state, in wire order, keyed by `toolCallKey` below.\n * A Map, not an object: the key comes off the wire, and on a plain `{}` a chunk\n * whose `index` is `\"__proto__\"` writes into Object.prototype for the whole page.\n */\n const toolCalls = new Map<string, { id: string; name: string; args: string }>();\n /** The call the last delta touched — what a bare argument fragment continues. */\n let lastToolKey: string | null = null;\n /**\n * Every key a delta has addressed a call by → that call's slot in `toolCalls`.\n * One call can be addressed both ways in one turn (by `index` in a chunk, by `id`\n * in the next), so the identity is the alias, not the key of the moment.\n */\n const toolCallAliases = new Map<string, string>();\n /** Ids minted for a vendor that sends none. Per stream, i.e. per turn. */\n let mintedCallIds = 0;\n let capturedUsage: AparteUsage | undefined;\n /** The last non-null `finish_reason` seen, mapped; every `done` carries it. */\n let finishReason: AparteFinishReason | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n /**\n * What the call has said so far, and what it cites. A citation's offsets are into the\n * whole message text, so the `sources` event waits for the end of the stream, when the\n * text is complete and the offsets can be checked against it.\n */\n let streamed = '';\n const cited = createSourceCollector();\n // Once, at the first of a tool call and the end: a call the loop refuses, or one with no\n // handler, ends the turn without reading on, and its sources would never arrive.\n let sourcesSent = false;\n const flushSources = (controller: ReadableStreamDefaultController<AparteStreamEvent>): void => {\n if (sourcesSent) return;\n sourcesSent = true;\n const event = cited.finish(streamed);\n if (event) controller.enqueue(event);\n };\n\n /*\n * Emit the accumulated calls and clear the map.\n *\n * Called at EVERY graceful end of a turn, not only on `finish_reason: 'tool_calls'`:\n * the accumulation spans the whole stream, and a vendor that ends with `stop`,\n * `length` or a bare `[DONE]` after streaming tool-call deltas used to drop them —\n * the model had asked for a tool and the loop never heard. `complete` says the\n * model declared the calls finished: a parse failure there is malformed JSON (a\n * real small-model failure) and the tool still runs, on `{}`. Elsewhere it is\n * truncation, and running a tool on `{}` for a call the model never finished would\n * be worse than dropping it — so those are dropped, with a breadcrumb.\n */\n const flushToolCalls = (controller: ReadableStreamDefaultController<AparteStreamEvent>, complete: boolean): void => {\n for (const entry of toolCalls.values()) {\n let input: Record<string, unknown> = {};\n try {\n input = entry.args.trim() ? JSON.parse(entry.args) : {};\n } catch {\n if (!complete) {\n console.warn(`[openai-compat] Tool \"${entry.name}\" was cut mid-arguments (the turn ended without finish_reason 'tool_calls'); the call is dropped. Raw:`, entry.args);\n continue;\n }\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 toolCalls.clear();\n toolCallAliases.clear();\n lastToolKey = null;\n };\n\n /*\n * Which accumulated call a `delta.tool_calls` entry belongs to.\n *\n * Both `index` and `id` address a call, and a vendor may use one in a chunk and\n * the other in the next — so neither can BE the identity: a delta's keys are\n * ALIASES that all resolve to the same slot. A key already resolved names the\n * call, and every key a delta carries is recorded against the slot it lands on.\n * Picking one key instead (`index` first, `id` next) split such a call in two,\n * the second half nameless, and the turn ran the tool on `{}`.\n *\n * When no key of the delta is known yet, the function NAME decides — it appears\n * only on a call's FIRST delta, so a nameless delta continues the call before it\n * and a named one opens its own. That single rule covers the vendor whose deltas\n * never carry both addresses at once (open by `id`, continue by `index`, or the\n * reverse) and the vendor that carries neither: `index` is a streaming\n * convenience, not part of the function-call payload, and keying on it alone\n * (`Number(tc.index ?? 0)`) put every call of a server that omits it on slot 0.\n *\n * `null` means \"skip this entry\": an `index` the vendor sent that is not a\n * non-negative integer is not a slot number, it is malformed (or hostile —\n * `\"__proto__\"` was the shape that made this a Map).\n */\n const toolCallKey = (tc: { index?: unknown; id?: string; function?: { name?: string } }): string | null => {\n let indexKey: string | undefined;\n if (tc.index !== undefined && tc.index !== null) {\n // Made a number, not annotated as one: `index` is whatever the vendor's JSON put there.\n const idx = Number(tc.index);\n if (!Number.isInteger(idx) || idx < 0) return null;\n indexKey = `i${idx}`;\n }\n const idKey = tc.id ? `d${tc.id}` : undefined;\n const known = (idKey ? toolCallAliases.get(idKey) : undefined) ?? (indexKey ? toolCallAliases.get(indexKey) : undefined);\n // No key of this delta is known yet. A nameless delta never OPENS a call, so it\n // continues the call before it — which is what keeps ONE call together when the\n // vendor opens it with one address and continues it with the other, neither\n // delta carrying both. A named delta still opens its own slot.\n const key = known ?? ((!tc.function?.name && lastToolKey !== null) ? lastToolKey : (idKey ?? indexKey));\n if (key === undefined) return `p${toolCalls.size}`;\n if (idKey) toolCallAliases.set(idKey, key);\n if (indexKey) toolCallAliases.set(indexKey, key);\n return key;\n };\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 // A turn that streamed tool-call deltas and ended on `stop`,\n // `length` or a bare [DONE]: the calls are still the model's.\n flushToolCalls(controller, false);\n flushSources(controller);\n controller.enqueue({ type: 'done', usage: capturedUsage, ...(finishReason ? { finishReason } : {}) });\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 // Perplexity's list of sources rides on any chunk, usage-only ones\n // included, which is why it is read before the choice is.\n cited.addListed(json.citations, json.search_results);\n\n // An error the server streamed AFTER a 200 (LM Studio on a context\n // overflow, OpenAI and vLLM mid-stream). Without this the line was\n // skipped as \"no choices\" and the turn ended on an empty `done`:\n // an empty reply marked completed. An `event: error` line before it\n // is ignored like every non-`data:` line; this data line is the payload.\n if (json.error) {\n controller.enqueue(errorEvent(json.error));\n reader?.cancel().catch(() => undefined);\n return;\n }\n\n const choice = json.choices?.[0];\n if (!choice) continue;\n if (typeof choice.finish_reason === 'string' && choice.finish_reason) {\n finishReason = toFinishReason(choice.finish_reason);\n }\n\n const delta = choice.delta;\n // Annotations: on the delta as OpenAI and OpenRouter stream them\n // (usually in a late chunk, after the text), or on a whole `message`\n // as some servers stream the non-streamed shape.\n cited.addAnnotations(delta?.annotations);\n cited.addAnnotations(choice.message?.annotations);\n if (delta) {\n if (delta.reasoning_content) {\n controller.enqueue({ type: 'thinking', delta: delta.reasoning_content });\n }\n if (delta.content) {\n streamed += 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 key = toolCallKey(tc);\n if (key === null) continue;\n let entry = toolCalls.get(key);\n if (!entry) {\n /*\n * An id is MINTED when the vendor sends none. `id: ''`\n * used to travel: the transcript keys a row on\n * `tool-${id}` and the history a result on\n * `toolCallId`, so two id-less calls in one turn shared\n * one row (the second call's result landed on the first\n * call's line) and one history slot an OpenAI-shaped\n * endpoint rejects on the next turn. A real id arriving\n * in a later chunk still wins, below. The mint is\n * namespaced because `call_1` is what an OpenAI-shaped\n * server calls its own first call: a turn mixing an\n * id-less call with a real `call_1` minted a duplicate.\n */\n entry = { id: tc.id ?? `aparte-call-${++mintedCallIds}`, name: tc.function?.name ?? '', args: '' };\n toolCalls.set(key, entry);\n }\n lastToolKey = key;\n if (tc.id) entry.id = tc.id;\n if (tc.function?.name) entry.name = tc.function.name;\n if (tc.function?.arguments) entry.args += tc.function.arguments;\n }\n }\n }\n\n // The model declared the calls complete: emit them now (and clear\n // the map, so a server that repeats the finish chunk cannot\n // re-emit them). Do NOT emit `done` or return here: under\n // `include_usage` (which buildRequest requests) the usage-only\n // chunk arrives AFTER this finish chunk, so stopping now dropped\n // usage on every tool-call turn — i.e. most turns of an agent.\n // Keep reading; the single `done` comes from `[DONE]` below, or\n // from the end-of-stream fallback if the socket just closes.\n if (choice.finish_reason === 'tool_calls') {\n flushSources(controller);\n flushToolCalls(controller, true);\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 // The socket closed without [DONE]: same rule, the calls are flushed first.\n flushToolCalls(controller, false);\n flushSources(controller);\n controller.enqueue({ type: 'done', usage: capturedUsage, ...(finishReason ? { finishReason } : {}) });\n } catch (err: unknown) {\n // AbortError surfaces here when the caller's signal fires mid-stream:\n // the consumer (agent loop) cancelled on purpose — end quietly, the\n // same way the ai-sdk provider does. Reporting it as an `error` event\n // makes a deliberate Stop indistinguishable from a network failure,\n // and the loop's error branch then replaces the streamed answer with\n // an error bubble.\n if ((err as { name?: string } | undefined)?.name !== 'AbortError') {\n controller.enqueue({ type: 'error', message: (err as Error | undefined)?.message ?? 'Stream error' });\n }\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.28.0",
3
+ "version": "0.28.3",
4
4
  "homepage": "https://apartejs.dev/providers/ai/openai-compat/",
5
5
  "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.",
6
6
  "type": "module",
@@ -25,13 +25,13 @@
25
25
  "node": ">=18"
26
26
  },
27
27
  "peerDependencies": {
28
- "@aparte/core": ">=0.28.0 <1.0.0"
28
+ "@aparte/core": ">=0.28.3 <1.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.0.0",
32
32
  "typescript": "^5.4.0",
33
33
  "vite": "^6.0.0",
34
- "@aparte/core": "0.28.0"
34
+ "@aparte/core": "0.28.3"
35
35
  },
36
36
  "keywords": [
37
37
  "ai",