dsh-magpie-connect 0.3.2 → 0.3.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.
- package/dsh.plugin.json +1 -1
- package/lib/client.js +1 -1
- package/lib/index.js +8 -3
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
package/dsh.plugin.json
CHANGED
package/lib/client.js
CHANGED
|
@@ -514,7 +514,7 @@ function createWriteQueue(write, onSettled) {
|
|
|
514
514
|
|
|
515
515
|
// src/client/page.tsx
|
|
516
516
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
517
|
-
var PLUGIN_VERSION = "0.3.
|
|
517
|
+
var PLUGIN_VERSION = "0.3.3";
|
|
518
518
|
var css2 = {
|
|
519
519
|
section: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 780, paddingBottom: 32 },
|
|
520
520
|
title: { margin: 0, fontSize: 22, lineHeight: "30px", fontWeight: 600, color: "var(--dsw-alias-label-primary)" },
|
package/lib/index.js
CHANGED
|
@@ -53,6 +53,11 @@ function requiresResponsesApiEntry(nativeEndpoints) {
|
|
|
53
53
|
function modelApiForEntry(nativeEndpoints) {
|
|
54
54
|
return requiresResponsesApiEntry(nativeEndpoints) ? "openai-responses" : "openai-completions";
|
|
55
55
|
}
|
|
56
|
+
function clampLaneWindow(id, value) {
|
|
57
|
+
if (value === void 0) return void 0;
|
|
58
|
+
if (id.startsWith("workbuddy-ai/")) return Math.min(value, 1e5);
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
56
61
|
function decodeModel(raw) {
|
|
57
62
|
if (!raw || typeof raw !== "object") return void 0;
|
|
58
63
|
const record = raw;
|
|
@@ -60,7 +65,7 @@ function decodeModel(raw) {
|
|
|
60
65
|
if (typeof id !== "string" || id.length === 0) return void 0;
|
|
61
66
|
const num = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
62
67
|
const displayName = typeof record.display_name === "string" && record.display_name || typeof record.magpie_label === "string" && record.magpie_label || id;
|
|
63
|
-
const contextWindow = num(record.context_window) ?? num(record.context_length) ?? num(record.max_input_tokens);
|
|
68
|
+
const contextWindow = clampLaneWindow(id, num(record.context_window) ?? num(record.context_length) ?? num(record.max_input_tokens));
|
|
64
69
|
const maxTokens = num(record.max_output_tokens);
|
|
65
70
|
const modalities = record.modalities;
|
|
66
71
|
const inputModalities = Array.isArray(modalities?.input) ? modalities.input.map(String).map((s) => s.toLowerCase()) : [];
|
|
@@ -103,8 +108,8 @@ var staticMagpieModels = [
|
|
|
103
108
|
{ id: "copilot/gpt-4.1", displayName: "gpt-4.1", contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: ["/v1/chat/completions", "/v1/responses", "/v1/messages"], reasoning: false },
|
|
104
109
|
{ id: "copilot/gpt-6-luna", displayName: "gpt-6-luna", contextWindow: 922e3, maxTokens: 128e3, image: true, nativeEndpoints: ["/v1/responses"], reasoning: true, efforts: ["none", "low", "medium", "high", "xhigh", "max"] },
|
|
105
110
|
{ id: "copilot/claude-sonnet-5.5", displayName: "claude-sonnet-5.5", contextWindow: 1e6, maxTokens: 128e3, image: true, nativeEndpoints: ["/v1/chat/completions", "/v1/messages"], reasoning: true, efforts: ["low", "medium", "high", "xhigh", "max"] },
|
|
106
|
-
{ id: "workbuddy-ai/hy4-preview-f", displayName: "hy4-preview-f", contextWindow:
|
|
107
|
-
{ id: "workbuddy-ai/deepseek-v4.1-flash", displayName: "deepseek-v4.1-flash", contextWindow:
|
|
111
|
+
{ id: "workbuddy-ai/hy4-preview-f", displayName: "hy4-preview-f", contextWindow: 1e5, maxTokens: 64e3, image: true, nativeEndpoints: ["/v1/chat/completions"], reasoning: true, efforts: ["high"] },
|
|
112
|
+
{ id: "workbuddy-ai/deepseek-v4.1-flash", displayName: "deepseek-v4.1-flash", contextWindow: 1e5, maxTokens: 128e3, image: true, nativeEndpoints: ["/v1/chat/completions"], reasoning: true, efforts: ["low", "high", "max"] }
|
|
108
113
|
];
|
|
109
114
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
110
115
|
var ModelCatalog = class {
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/adapter/catalog.ts", "../src/adapter/magpie-adapter.ts", "../src/adapter/events.ts", "../src/adapter/messages.ts", "../src/adapter/watchdog.ts", "../src/config.ts", "../src/settings.ts"],
|
|
4
|
-
"sourcesContent": ["import { join } from 'node:path'\nimport { writeFile } from 'node:fs/promises'\n\nimport {\n ModelCatalog,\n defaultCachePath,\n fetchMagpieModels,\n type CatalogSnapshot,\n type MagpieModelEntry,\n} from './adapter/catalog.ts'\nimport { MagpieAdapter } from './adapter/magpie-adapter.ts'\nimport type { AttachmentStore } from './adapter/messages.ts'\nimport { resolveConfig, type DshMagpieConnectConfig } from './config.ts'\nimport {\n SettingsStore,\n defaultSettingsPath,\n isConfigured,\n normalizeBaseUrl,\n normalizePageSettings,\n resolveEffectiveEndpoint,\n} from './settings.ts'\n\n/**\n * dsh-magpie-connect DSH cordis plugin entry.\n *\n * Registers a DSH LlmAdapter streaming directly from the Magpie LAN gateway\n * (marketplace shape: no child process, no binary, no proxy). The model\n * catalog warms up in the background (live gateway list with disk cache +\n * static snapshot fallback).\n *\n * The Settings sidebar page (client half) talks to the REST routes below:\n * connection endpoint (baseUrl/apiKey) applies live without restart, and the\n * hidden-model list filters the picker immediately.\n *\n * dispose(): stop the catalog refresh loop. The cordis fiber disposal\n * guarantees this runs on plugin reload/unload and on DSH shutdown.\n */\n\n// Minimal structural typing against the host ctx; keeps the plugin independent\n// of the exact @deepseek-ai/cordis version DSH ships.\nexport interface PluginContext {\n logger: { info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void }\n llm?: { registerAdapter(providers: string[], adapter: unknown): unknown }\n get?(key: string): unknown\n inject?(deps: string[], fn: (ctx: Record<string, unknown>) => unknown): unknown\n effect?(fn: () => () => void): unknown\n on?(event: string, listener: (...args: never[]) => unknown): () => void\n /**\n * Cordis `ctx.emit` (mixed in from the events service). Used to publish\n * `llm/adapters-updated` when this plugin's own catalog moves, without\n * touching the adapter registry.\n */\n emit?(event: string, ...args: unknown[]): unknown\n}\n\nexport const name = 'dsh-magpie-connect'\nexport const inject = ['llm'] as const\n\n/** Browser-facing REST root for the settings page (served under the shared /api channel). */\nexport const SETTINGS_API = '/api/magpie-settings'\nexport const MODELS_API = '/api/magpie-models'\nexport const TEST_API = '/api/magpie-test'\n/**\n * Candidate discovery: asks the endpoint the form currently shows (including a\n * key typed but not yet saved) what it serves. The reply is candidates the\n * user picks from \u2014 never configuration written behind them.\n */\nexport const DISCOVER_API = '/api/magpie-discover'\n\n/**\n * How long a settings save waits for the catalog re-read it triggered.\n *\n * The page re-reads rows right after the response, so the save must not answer\n * before a fast gateway has been read; but the credential write is already\n * durable, so a slow gateway must not hold the request open either.\n */\nconst SAVE_REFRESH_WAIT_MS = 5000\n\n/** Backend generation behind the shared settings routes (newest wins). */\nexport interface SettingsBackend {\n store: SettingsStore\n catalog: ModelCatalog\n patchEndpoint: { baseUrl: string; apiKey: string }\n effective: () => { baseUrl: string; apiKey: string }\n applyPageSettings: () => void\n}\n\ntype FetchRegister = (route: {\n path: string\n methods: string[]\n requestBody: string\n fetch: (request: Request) => Promise<Response>\n}) => () => void | Promise<void>\n\ninterface ModelRow {\n id: string\n displayName: string\n contextWindow?: number\n maxTokens?: number\n image: boolean\n responsesOnly: boolean\n reasoning: boolean\n efforts: string[]\n hidden: boolean\n}\n\n/** One discovered candidate: a {@link ModelRow} without this plugin's own visibility flag. */\nexport type CandidateRow = Omit<ModelRow, 'hidden'>\n\n/** True when the endpoints advertise responses but not chat completions. */\nfunction responsesOnlyFor(nativeEndpoints: readonly string[]): boolean {\n if (nativeEndpoints.length === 0) return false\n const hasCompletions = nativeEndpoints.some((endpoint) => endpoint.includes('/chat/completions'))\n return !hasCompletions && nativeEndpoints.some((endpoint) => endpoint.includes('/responses'))\n}\n\n/** Project one catalog entry (or a bare id) into the wire row the page renders. */\nfunction toRow(id: string, entry: MagpieModelEntry | undefined): CandidateRow {\n return {\n id,\n displayName: entry?.displayName ?? id,\n ...(entry?.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),\n ...(entry?.maxTokens !== undefined ? { maxTokens: entry.maxTokens } : {}),\n image: entry?.image ?? false,\n responsesOnly: responsesOnlyFor(entry?.nativeEndpoints ?? []),\n reasoning: entry?.reasoning ?? false,\n efforts: entry?.efforts ?? [],\n }\n}\n\nlet currentBackend: SettingsBackend | null = null\nlet liveRouteSets = 0\nlet routeDisposers: Array<() => void | Promise<void>> | null = null\n\n/** @internal Test hook: forget installed routes so the next apply reinstalls. */\nexport function __resetSettingsRoutes(): void {\n currentBackend = null\n liveRouteSets = 0\n routeDisposers = null\n}\n\nfunction jsonResponse(status: number, payload: unknown): Response {\n return new Response(JSON.stringify(payload), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },\n })\n}\n\nconst routeOk = (value: unknown): Response => jsonResponse(200, { ok: true, value })\nconst routeFail = (error: unknown): Response =>\n jsonResponse(500, { ok: false, error: error instanceof Error ? error.message : String(error) })\n\nasync function readRouteBody(request: Request): Promise<unknown> {\n try {\n return await request.json()\n } catch {\n return {}\n }\n}\n\nfunction buildModelRows(backend: SettingsBackend): ModelRow[] {\n const { store, catalog } = backend\n const hidden = new Set(store.get().hiddenModels ?? [])\n // Hidden ids survive in the list even when the gateway no longer serves\n // them: a stale entry must stay reachable so it can be un-hidden again.\n const ids = new Set<string>([...catalog.list(), ...hidden])\n return [...ids].sort().map((id) => ({ ...toRow(id, catalog.getEntry(id)), hidden: hidden.has(id) }))\n}\n\nfunction backendOrThrow(): SettingsBackend {\n if (!currentBackend) throw new Error('dsh-magpie-connect: settings backend not ready')\n return currentBackend\n}\n\n/**\n * One registered route: the connection registry keys routes by PATH and\n * rejects a second registration of the same path, so every method of one\n * endpoint must share a single entry and dispatch inside its handler. Two\n * entries on `SETTINGS_API` (GET + POST) silently lose the second one \u2014 the\n * save button then answers `404 not found` forever.\n */\ninterface RouteDefinition {\n path: string\n methods: string[]\n requestBody: string\n fetch: (request: Request) => Promise<Response>\n}\n\nasync function handleSettingsGet(): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const page = backend.store.get()\n const endpoint = resolveEffectiveEndpoint(backend.patchEndpoint, page)\n return routeOk({\n baseUrl: endpoint.baseUrl,\n apiKey: endpoint.apiKey,\n hiddenModels: page.hiddenModels ?? [],\n fromPage: page,\n fromPatch: backend.patchEndpoint,\n })\n } catch (error) {\n return routeFail(error)\n }\n}\n\nasync function handleSettingsPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = await readRouteBody(request)\n const normalized = normalizePageSettings(body)\n // Prune ids the gateway no longer serves: the page only ever shows live\n // rows, so a stale hidden id could otherwise never be seen or cleared.\n if (normalized.hiddenModels !== undefined) {\n normalized.hiddenModels = backend.catalog.pruneHidden(normalized.hiddenModels)\n }\n const saved = await backend.store.save(normalized)\n backend.applyPageSettings()\n // A save can move the endpoint (or just the key), so re-read the directory\n // before answering: the page re-reads rows right after this response, and a\n // key-guarded gateway would otherwise still be serving the old, empty\n // snapshot when that read lands.\n //\n // Bounded: the write is already durable, so a gateway that answers slowly\n // (or not at all) must not turn a successful save into a client-side\n // timeout. Losing the race only costs a stale row, never the setting.\n if (normalized.baseUrl !== undefined || normalized.apiKey !== undefined) {\n await Promise.race([\n backend.catalog.refreshOnce(),\n new Promise((resolve) => setTimeout(resolve, SAVE_REFRESH_WAIT_MS)),\n ])\n }\n const endpoint = resolveEffectiveEndpoint(backend.patchEndpoint, saved)\n return routeOk({ baseUrl: endpoint.baseUrl, apiKey: endpoint.apiKey, hiddenModels: saved.hiddenModels ?? [] })\n } catch (error) {\n return routeFail(error)\n }\n}\n\nasync function handleModelsGet(): Promise<Response> {\n try {\n const backend = backendOrThrow()\n return routeOk({ models: buildModelRows(backend), snapshot: backend.catalog.snapshot() })\n } catch (error) {\n return routeFail(error)\n }\n}\n\n/** Resolve the endpoint one probe should ask: form values first, effective config as fallback. */\nfunction probeTarget(\n backend: SettingsBackend,\n body: { baseUrl?: unknown; apiKey?: unknown },\n): { origin: string; key: string } {\n const effective = backend.effective()\n const origin = normalizeBaseUrl(body.baseUrl ?? effective.baseUrl)\n const rawKey = typeof body.apiKey === 'string' ? body.apiKey : effective.apiKey\n const key = rawKey.trim()\n if (key === '') throw new Error('dsh-magpie-connect: apiKey is required \u2014 fill it in before asking the gateway')\n return { origin, key }\n}\n\n/**\n * Turn a gateway failure into copy the page can show. A 404 from the gateway\n * almost always means the origin path is wrong or the key is unknown or\n * unauthorized (many gateways hide existence behind 404 instead of 401).\n */\nfunction hintGatewayFailure(error: unknown): Response {\n const message = error instanceof Error ? error.message : String(error)\n const hinted =\n /HTTP 404/.test(message) && !/\u7F51\u5173\u8FD4\u56DE 404/.test(message)\n ? `${message}\uFF08\u7F51\u5173\u8FD4\u56DE 404\uFF1A\u591A\u4E3A API \u5730\u5740\u8DEF\u5F84\u4E0D\u5BF9\uFF0C\u6216\u8BE5 Key \u65E0\u6548/\u65E0\u6743\u8BBF\u95EE\uFF09`\n : message\n return routeFail(hinted)\n}\n\nasync function handleTestPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = (await readRouteBody(request)) as { baseUrl?: unknown; apiKey?: unknown }\n const { origin, key } = probeTarget(backend, body)\n const models = await fetchMagpieModels(origin, fetch, { headers: { authorization: `Bearer ${key}` } })\n return routeOk({ count: models.size, models: [...models.keys()].sort().slice(0, 50) })\n } catch (error) {\n return hintGatewayFailure(error)\n }\n}\n\n/**\n * Candidate discovery for the fetch-available-models dialog.\n *\n * Unlike {@link handleTestPost} (a count for the connection check) this returns\n * full rows so the picker can show the same chips the list does, and it caps\n * nothing: the dialog filters client-side. The origin and key come from the\n * form, so a provider can be filled in one pass instead of save-then-return.\n */\nasync function handleDiscoverPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = (await readRouteBody(request)) as { baseUrl?: unknown; apiKey?: unknown }\n const { origin, key } = probeTarget(backend, body)\n const models = await fetchMagpieModels(origin, fetch, { headers: { authorization: `Bearer ${key}` } })\n const candidates = [...models.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([id, entry]) => toRow(id, entry))\n return routeOk({ count: candidates.length, models: candidates })\n } catch (error) {\n return hintGatewayFailure(error)\n }\n}\n\nfunction routeDefinitions(): RouteDefinition[] {\n return [\n {\n path: SETTINGS_API,\n methods: ['GET', 'POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => (request.method === 'POST' ? handleSettingsPost(request) : handleSettingsGet()),\n },\n {\n path: MODELS_API,\n methods: ['GET'],\n requestBody: 'buffered',\n fetch: () => handleModelsGet(),\n },\n {\n path: TEST_API,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => handleTestPost(request),\n },\n {\n path: DISCOVER_API,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => handleDiscoverPost(request),\n },\n ]\n}\n\n/**\n * Install the settings-page routes on the authenticated /api channel.\n *\n * Process-wide singleton semantics: hot reload can run overlapping\n * generations of this plugin in one process, and the connection registry\n * rejects duplicate paths. The routes install once and are shared; the last\n * generation out uninstalls. Handlers resolve through `currentBackend`, so\n * they always serve the newest generation.\n */\nfunction installSettingsRoutes(\n ctx: PluginContext,\n logger: PluginContext['logger'],\n): void {\n try {\n if (typeof ctx.inject !== 'function') return\n ctx.inject(['connection'], (cctx: Record<string, unknown>) => {\n const connection = cctx.connection as { fetch?: { register?: FetchRegister } } | undefined\n const register = connection?.fetch?.register\n if (typeof register !== 'function') {\n logger.warn('dsh-magpie-connect: connection fetch registry unavailable; settings page API disabled')\n return\n }\n liveRouteSets += 1\n try {\n if (routeDisposers === null) {\n const installed: Array<() => void | Promise<void>> = []\n try {\n for (const definition of routeDefinitions()) {\n try {\n installed.push(register(definition))\n } catch (error) {\n // A live sibling generation already owns this path; share it.\n // Only ever expected for a whole path we also declare \u2014 since\n // every method of one path shares one entry, losing one here\n // means the sibling serves it identically.\n if (error instanceof Error && /already registered/.test(error.message)) {\n logger.warn(`dsh-magpie-connect: ${definition.path} already registered by another generation; sharing it`)\n continue\n }\n throw error\n }\n }\n } catch (error) {\n for (const dispose of installed.reverse()) {\n try {\n const result = dispose()\n if (result instanceof Promise) result.catch(() => {})\n } catch {\n // roll back best-effort\n }\n }\n throw error\n }\n routeDisposers = installed\n }\n } catch (error) {\n liveRouteSets -= 1\n logger.warn(`dsh-magpie-connect: settings API unavailable: ${error instanceof Error ? error.message : String(error)}`)\n throw error\n }\n return async () => {\n liveRouteSets -= 1\n if (liveRouteSets <= 0) {\n liveRouteSets = 0\n const owned = routeDisposers\n routeDisposers = null\n if (owned) {\n for (const dispose of owned.reverse()) await dispose()\n }\n }\n }\n })\n } catch (error) {\n logger.warn(`dsh-magpie-connect: settings API unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\nexport function apply(\n ctx: PluginContext,\n config: DshMagpieConnectConfig = {},\n): { ready: Promise<{ version: string }> } {\n const logger = ctx.logger\n const cfg = resolveConfig(config)\n const ready = Promise.resolve({ version: 'adapter' })\n\n if (!ctx.llm || typeof ctx.llm.registerAdapter !== 'function') {\n logger.error('dsh-magpie-connect: llm service unavailable; adapter cannot register')\n return { ready }\n }\n\n const dataDir = cfg.dataDir\n const statusPath = join(dataDir, 'adapter-status.json')\n const writeStatus = (status: CatalogSnapshot, lastError: string): void => {\n void writeFile(\n statusPath,\n JSON.stringify({ ...status, lastError, writtenAt: new Date().toISOString() }, null, 2),\n 'utf8',\n ).catch(() => {})\n }\n\n const store = new SettingsStore({ path: defaultSettingsPath(dataDir) })\n const patchEndpoint = { baseUrl: cfg.baseUrl, apiKey: cfg.apiKey }\n const effective = () => resolveEffectiveEndpoint(patchEndpoint, store.get())\n\n /**\n * Publish `llm/adapters-updated`, the one notification DSH's model\n * directory listens to.\n *\n * The browser caches one `modelCatalog()` read per Host generation and\n * re-reads only on this event. Registering an adapter fires it once, but\n * nothing does for a pure in-memory change, so hiding models, switching\n * the gateway origin, or a refresh that moved the list all left the picker\n * on the stale snapshot until restart. `dsh-workbuddy-connect` solves the\n * same problem identically for its visibility toggles.\n */\n const announceTopology = (): void => {\n if (typeof ctx.emit !== 'function') return\n try {\n ctx.emit('llm/adapters-updated')\n } catch (error) {\n logger.warn(`dsh-magpie-connect: llm/adapters-updated emit failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n const catalog = new ModelCatalog({\n baseUrl: effective().baseUrl,\n apiKey: effective().apiKey,\n refreshSeconds: cfg.refreshSeconds,\n cachePath: defaultCachePath(dataDir),\n onRefresh: (status, lastError) => {\n writeStatus(status, lastError)\n if (lastError) logger.warn(`dsh-magpie-connect: catalog refresh issue: ${lastError}`)\n },\n onInvalidate: announceTopology,\n })\n /**\n * Apply page settings to the live catalog + adapter (no restart needed).\n *\n * The endpoint is a pair: the gateway guards `GET /models` with the same key\n * as inference, so the key is part of the catalog's identity, not just the\n * URL. A key-only edit therefore re-points the catalog too \u2014 otherwise the\n * refresh keeps 401ing and the picker keeps serving the static snapshot,\n * which is why a model just ticked in the fetch dialog never appeared.\n */\n const applyPageSettings = (): void => {\n const page = store.get()\n const endpoint = resolveEffectiveEndpoint(patchEndpoint, page)\n // The gate: URL and key are both required. Anything missing clears the\n // catalog API root, which empties the picker and calls fail fast.\n // The value is the versioned root (`\u2026/v1`), passed through untouched.\n const configured = isConfigured(endpoint)\n catalog.setEndpoint(configured ? endpoint.baseUrl.replace(/\\/+$/, '') : '', configured ? endpoint.apiKey : '')\n catalog.setHidden(page.hiddenModels ?? [])\n }\n const adapter = new MagpieAdapter(catalog, {\n providerId: cfg.providerId,\n displayName: cfg.displayName,\n magpieBaseUrl: cfg.baseUrl,\n apiKey: cfg.apiKey,\n maxRetries: cfg.maxRetries,\n timeoutMs: cfg.timeoutMs,\n firstEventTimeoutMs: cfg.firstEventTimeoutMs,\n idleTimeoutMs: cfg.idleTimeoutMs,\n runtime: {\n baseUrl: () => {\n const endpoint = effective()\n return isConfigured(endpoint) ? endpoint.baseUrl.replace(/\\/+$/, '') : ''\n },\n apiKey: () => effective().apiKey,\n },\n resolveAttachments: () => {\n try {\n const attachments = typeof ctx.get === 'function' ? (ctx.get('attachments') as AttachmentStore | undefined) : undefined\n return attachments && typeof attachments.readImageRequest === 'function' ? attachments : undefined\n } catch {\n return undefined\n }\n },\n })\n store.onChange(applyPageSettings)\n\n // Register immediately: the provider must appear in the selector right\n // away, even while the catalog is still warming up (listModels is read\n // live at selector time, so models appear as refreshes land).\n ctx.llm.registerAdapter([cfg.providerId], adapter)\n logger.info(`dsh-magpie-connect: adapter registered for \"${cfg.providerId}\" (catalog warms up in background)`)\n void (async () => {\n await store.load()\n applyPageSettings()\n await catalog.start().catch((err) => {\n logger.error(`dsh-magpie-connect: catalog start failed: ${err instanceof Error ? err.message : String(err)}`)\n })\n })().catch((err) => {\n logger.error(`dsh-magpie-connect: startup failed: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n // Suppress the auto-generated config form: this plugin owns a dedicated\n // settings page (client half). Guarded \u2014 compositions without the settings\n // service simply skip it.\n try {\n if (typeof ctx.inject === 'function') {\n ctx.inject(['settings'], (settingsCtx: Record<string, unknown>) => {\n try {\n const scoped = settingsCtx as {\n effect?: (fn: () => unknown, label?: string) => unknown\n settings?: { configure?: (policy: unknown, fiber: unknown) => unknown }\n }\n const fiber = (ctx as unknown as Record<string, unknown>).fiber\n scoped.effect?.(() => scoped.settings?.configure?.({ auto: false }, fiber), 'dsh-magpie-connect: custom settings page')\n } catch {\n // presentation policy is best-effort\n }\n })\n }\n } catch {\n // presentation policy is best-effort\n }\n\n // Newest generation wins: shared route handlers below always resolve through\n // here, so the settings page talks to the latest store/catalog/adapter even\n // when an older generation installed the routes.\n currentBackend = { store, catalog, patchEndpoint, effective, applyPageSettings }\n\n installSettingsRoutes(ctx, logger)\n\n const maybeEffect = (ctx as { effect?: PluginContext['effect'] }).effect\n if (typeof maybeEffect === 'function') {\n maybeEffect.call(ctx, () => () => {\n catalog.stop()\n })\n }\n return { ready }\n}\n\nexport { MagpieAdapter } from './adapter/magpie-adapter.ts'\nexport { ModelCatalog } from './adapter/catalog.ts'\nexport { SettingsStore, isConfigured, resolveEffectiveEndpoint } from './settings.ts'\nexport {\n resolveConfig,\n type DshMagpieConnectConfig,\n} from './config.ts'\n", "import { readFile, rename, rm, writeFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Model directory for the Magpie LAN gateway.\n *\n * Single source: `GET {baseUrl}/models` already curates the servable set\n * with full per-model metadata (modalities, native endpoints, reasoning\n * ladders, context limits). There is no paid/free filter \u2014 everything listed\n * is exposed. A disk cache plus a compile-time static snapshot covers gateway\n * outages (the plugin still registers, the picker still lists).\n *\n * The gateway guards that endpoint with the same Bearer key as inference, so\n * every fetch here carries it: a keyless refresh is answered 401 and the\n * picker silently keeps the static snapshot instead of the live directory.\n *\n * `baseUrl` is the versioned API root (default `http://127.0.0.1:3425/v1`),\n * never an origin: the gateway may move to `/v2`, and the plugin must not know\n * that version.\n */\n\nexport interface MagpieModelEntry {\n id: string\n displayName: string\n contextWindow?: number\n maxTokens?: number\n /** Whether the gateway accepts image input for this model. */\n image: boolean\n /** Raw gateway `native_endpoints` (may be empty when undisclosed). */\n nativeEndpoints: string[]\n /** Whether the gateway reports thinking support. */\n reasoning: boolean\n /** Effort allowlist from `supported_reasoning_levels` (canonical order). */\n efforts?: string[]\n}\n\n/**\n * Thinking levels the gateway uses. pi-ai natively understands the minimal\u2192max\n * ladder; `none` (thinking disabled) and `ultra` (beyond max, e.g. Terra) are\n * gateway extensions the adapter forwards verbatim on the Responses /\n * completions wire instead of clamping.\n */\nexport const KNOWN_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const\nexport const EXTENDED_EFFORTS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const\n\nexport type KnownEffort = (typeof KNOWN_EFFORTS)[number]\nexport type ExtendedEffort = (typeof EXTENDED_EFFORTS)[number]\n\n/** Picker display order: `none` (disable) first, then increasing strength. */\nconst EFFORT_ORDER: readonly string[] = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']\n\nconst EFFORT_LABELS: Record<string, { name: string; description: string }> = {\n none: { name: 'None', description: '\u5173\u95ED\u601D\u8003\uFF0C\u76F4\u63A5\u56DE\u7B54' },\n minimal: { name: 'Minimal', description: '\u6700\u5C11\u601D\u8003\uFF0C\u6700\u5FEB' },\n low: { name: 'Low', description: '\u8F7B\u91CF\u601D\u8003\uFF0C\u9002\u5408\u7B80\u5355\u95EE\u7B54' },\n medium: { name: 'Medium', description: '\u9ED8\u8BA4\u6863\u4F4D\uFF0C\u5747\u8861\u8D28\u91CF\u4E0E\u901F\u5EA6' },\n high: { name: 'High', description: '\u6DF1\u5EA6\u601D\u8003\uFF0C\u9002\u5408\u590D\u6742\u7F16\u7801' },\n xhigh: { name: 'Extra high', description: '\u8D85\u9AD8\u601D\u8003\u5F3A\u5EA6' },\n max: { name: 'Max', description: '\u6700\u5F3A\u601D\u8003\uFF08pi-ai \u6863\u4F4D\uFF09\uFF0C\u6700\u6162' },\n ultra: { name: 'Ultra', description: '\u6781\u9650\u601D\u8003\uFF08\u7F51\u5173\u76F4\u901A\uFF09\uFF0C\u6700\u6162\u6700\u8D35' },\n}\n\nexport interface ReasoningEffortChoice {\n id: string\n name: string\n description?: string\n}\n\n/** Normalize one gateway effort spelling; unknown spellings are dropped. */\nexport function normalizeEffort(raw: unknown): string | undefined {\n if (typeof raw !== 'string') return undefined\n const level = raw.trim().toLowerCase()\n return (EFFORT_ORDER as readonly string[]).includes(level) ? level : undefined\n}\n\n/** Canonicalize an effort list: keep known spellings, dedupe, order canonically. */\nexport function canonicalEfforts(values: readonly unknown[] | undefined): string[] | undefined {\n if (!Array.isArray(values)) return undefined\n const out: string[] = []\n for (const value of values) {\n const level = normalizeEffort(typeof value === 'object' && value !== null ? (value as { effort?: unknown }).effort ?? value : value)\n if (level !== undefined && !out.includes(level)) out.push(level)\n }\n if (out.length === 0) return undefined\n return EFFORT_ORDER.filter((level) => out.includes(level))\n}\n\n/** Build picker efforts for an allowlist (display order = none\u2192minimal\u2192\u2026\u2192ultra). */\nexport function effortsFor(efforts: readonly string[] | undefined): ReasoningEffortChoice[] {\n const allowed = new Set(efforts ?? [])\n return EFFORT_ORDER.filter((level) => allowed.has(level)).map((level) => {\n const label = EFFORT_LABELS[level] ?? { name: level, description: '' }\n return { id: level, name: label.name, description: label.description }\n })\n}\n\n/**\n * pi-ai `thinkingLevelMap` for an effort allowlist. `off: null` is\n * load-bearing: without it pi-ai falls back to `reasoning.effort: 'none'`\n * on the Responses lane, which the gateway rejects with 400. Every\n * non-listed known level maps to null so `getSupportedThinkingLevels` hides\n * it and `clampThinkingLevel` folds stale values to the nearest listed one.\n * Gateway extensions (`none`/`ultra`) never enter the map \u2014 the adapter\n * forwards them via `provider.stream` instead of `streamSimple`.\n */\nexport function thinkingLevelMapFor(efforts: readonly string[] | undefined): Record<string, string | null> {\n const allowed = new Set(efforts ?? [])\n const map: Record<string, string | null> = { off: null }\n for (const level of KNOWN_EFFORTS) map[level] = allowed.has(level) ? level : null\n return map\n}\n\n/**\n * True when the model must go through `/v1/responses` instead of chat\n * completions: the gateway lists responses as the only native endpoint\n * (Muse Spark, Codex/Grok lanes). Anything advertising\n * `/v1/chat/completions` \u2014 including multi-endpoint models \u2014 rides the\n * completions lane for better tool support.\n */\nexport function requiresResponsesApiEntry(nativeEndpoints: readonly string[]): boolean {\n const hasCompletions = nativeEndpoints.some((endpoint) => endpoint.includes('/chat/completions'))\n if (hasCompletions) return false\n return nativeEndpoints.some((endpoint) => endpoint.includes('/responses'))\n}\n\n/** pi-ai `model.api` value for the given native endpoints. */\nexport function modelApiForEntry(nativeEndpoints: readonly string[]): 'openai-completions' | 'openai-responses' {\n return requiresResponsesApiEntry(nativeEndpoints) ? 'openai-responses' : 'openai-completions'\n}\n\n/** Decode one gateway model object into a catalog entry. */\nexport function decodeModel(raw: unknown): MagpieModelEntry | undefined {\n if (!raw || typeof raw !== 'object') return undefined\n const record = raw as Record<string, unknown>\n const id = record.id\n if (typeof id !== 'string' || id.length === 0) return undefined\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined\n const displayName =\n (typeof record.display_name === 'string' && record.display_name) ||\n (typeof record.magpie_label === 'string' && record.magpie_label) ||\n id\n const contextWindow = num(record.context_window) ?? num(record.context_length) ?? num(record.max_input_tokens)\n const maxTokens = num(record.max_output_tokens)\n const modalities = record.modalities as { input?: unknown } | undefined\n const inputModalities = Array.isArray(modalities?.input) ? modalities.input.map(String).map((s) => s.toLowerCase()) : []\n const image = inputModalities.includes('image')\n const nativeEndpoints = Array.isArray(record.native_endpoints)\n ? (record.native_endpoints as unknown[]).filter((e): e is string => typeof e === 'string')\n : []\n const reasoning = record.reasoning === true\n const levels = record.supported_reasoning_levels\n const efforts = Array.isArray(levels)\n ? canonicalEfforts(levels.map((entry) => (entry as { effort?: unknown })?.effort ?? entry))\n : undefined\n return { id, displayName, ...(contextWindow !== undefined ? { contextWindow } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), image, nativeEndpoints, reasoning, ...(efforts !== undefined ? { efforts } : {}) }\n}\n\n/** Decode the full gateway `/v1/models` payload into entries keyed by id. */\nexport function decodeMagpieModels(data: unknown): Map<string, MagpieModelEntry> {\n const result = new Map<string, MagpieModelEntry>()\n if (!data || typeof data !== 'object') return result\n const payload = data as { data?: unknown }\n if (!Array.isArray(payload.data)) return result\n for (const raw of payload.data) {\n const entry = decodeModel(raw)\n if (entry && !result.has(entry.id)) result.set(entry.id, entry)\n }\n return result\n}\n\n/**\n * Compile-time static snapshot (observed 2026-10-02, 22 models): last-resort\n * bootstrap while the gateway is unreachable. Refreshed by hand when the\n * gateway catalog changes shape; live data always wins when reachable.\n */\nexport const staticMagpieModels: MagpieModelEntry[] = [\n { id: 'group/auto-gpt-4-1', displayName: 'GPT-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: [], reasoning: false },\n { id: 'group/auto-gpt-6-luna', displayName: 'GPT-6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: [], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'vercel/xiaomi/mimo-v2.6-flash', displayName: 'xiaomi/mimo-v2.6-flash', contextWindow: 1048576, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true },\n { id: 'vercel/alibaba/qwen3.8-omni-flash', displayName: 'alibaba/qwen3.8-omni-flash', contextWindow: 1000000, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['none', 'low', 'medium', 'xhigh'] },\n { id: 'vercel/openai/gpt-5.4-nano', displayName: 'openai/gpt-5.4-nano', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['none', 'low', 'medium', 'high', 'xhigh'] },\n { id: 'vercel/openai/gpt-4.1', displayName: 'openai/gpt-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: false },\n { id: 'vercel/google/gemini-2.5-flash-lite', displayName: 'google/gemini-2.5-flash-lite', contextWindow: 1048576, maxTokens: 65536, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'medium', 'high'] },\n { id: 'opencode-zen/muse-spark-1.3-contributor-free', displayName: 'Muse Spark 1.3 Free', contextWindow: 1048576, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['minimal', 'low', 'medium', 'high', 'xhigh'] },\n { id: 'opencode-zen/jev-1.13-free', displayName: 'jev-1.13-free', image: false, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: false },\n { id: 'opencode-zen/mimo-v2.6-flash-free', displayName: 'MiMo-V2.6-Flash Free', contextWindow: 200000, maxTokens: 32000, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: true },\n { id: 'opencode-zen/space-bunny-free', displayName: 'Space Bunny Free', contextWindow: 524288, maxTokens: 524288, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-6-luna', displayName: 'GPT-6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-5.6-terra', displayName: 'GPT-5.6-Terra', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'] },\n { id: 'codex/gpt-5.6-luna', displayName: 'GPT-5.6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-5.5', displayName: 'GPT-5.5', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh'] },\n { id: 'copilot/gemini-3.8-flash', displayName: 'Gemini 3.8 Flash', contextWindow: 1048576, maxTokens: 65536, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'medium', 'high'] },\n { id: 'copilot/grok-4.7', displayName: 'grok-4.7', contextWindow: 500000, maxTokens: 500000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh'] },\n { id: 'copilot/gpt-4.1', displayName: 'gpt-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: false },\n { id: 'copilot/gpt-6-luna', displayName: 'gpt-6-luna', contextWindow: 922000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'copilot/claude-sonnet-5.5', displayName: 'claude-sonnet-5.5', contextWindow: 1000000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/messages'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'workbuddy-ai/hy4-preview-f', displayName: 'hy4-preview-f', contextWindow: 1000000, maxTokens: 64000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['high'] },\n { id: 'workbuddy-ai/deepseek-v4.1-flash', displayName: 'deepseek-v4.1-flash', contextWindow: 1000000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'high', 'max'] },\n]\n\nexport interface CatalogSnapshot {\n status: 'pending' | 'ready' | 'stale' | 'error'\n total: number\n exposed: number\n lastRefresh?: string\n}\n\nexport interface CatalogOptions {\n /** Refresh cadence (seconds). */\n refreshSeconds?: number\n /** Where the gateway snapshot cache lives (plugin data dir). */\n cachePath?: string\n /** Versioned API root override for tests. */\n baseUrl?: string\n /**\n * Gateway credential, sent as `Authorization: Bearer \u2026` on every catalog\n * fetch. The gateway guards `GET /models` with the same key it guards\n * inference with, so a refresh that omits it is answered 401 and the picker\n * silently stays on the static snapshot.\n */\n apiKey?: string\n fetchImpl?: typeof fetch\n now?: () => number\n /** Observability hook: fired after every refresh round. */\n onRefresh?: (status: CatalogSnapshot, lastError: string) => void\n /**\n * Fired when the exposed model set actually changes. DSH's picker caches\n * one `modelCatalog()` read per Host generation and only re-reads it on\n * `llm/adapters-updated`, so a pure in-memory change (hidden models, a\n * refresh that added or dropped models) is invisible without this.\n */\n onInvalidate?: () => void\n /** Delay between startup retries while the live catalog is empty (default 15s). */\n startupRetryMs?: number\n}\n\nconst FETCH_TIMEOUT_MS = 30_000\n\n/** Live model directory with refresh loop. All state in-memory; only the gateway snapshot persists. */\nexport class ModelCatalog {\n #entries: Map<string, MagpieModelEntry> = new Map()\n #hidden: Set<string> = new Set()\n #updatedAt = 0\n #lastError = ''\n #refreshSeconds: number\n #cachePath?: string\n #baseUrl: string\n #apiKey: string\n #fetch: typeof fetch\n #now: () => number\n #timer: NodeJS.Timeout | null = null\n #stopped = false\n #onRefresh?: (status: CatalogSnapshot, lastError: string) => void\n #onInvalidate?: () => void\n #startupRetryMs: number\n /**\n * Last announced exposed set. The picker caches its catalog read, so an\n * invalidation is only worth a Host event when the exposed ids really\n * moved \u2014 otherwise the refresh loop would emit every 5 minutes.\n */\n #announced = ''\n\n constructor(options: CatalogOptions = {}) {\n this.#refreshSeconds = options.refreshSeconds ?? 300\n this.#cachePath = options.cachePath\n this.#baseUrl = (options.baseUrl ?? '').replace(/\\/+$/, '')\n this.#apiKey = options.apiKey ?? ''\n this.#fetch = options.fetchImpl ?? fetch\n this.#now = options.now ?? Date.now\n this.#onRefresh = options.onRefresh\n this.#onInvalidate = options.onInvalidate\n this.#startupRetryMs = options.startupRetryMs ?? 15_000\n }\n\n /**\n * Start the refresh loop: immediate fetch, fast retries while the live\n * catalog is still empty (the first fetch often races the machine's\n * network coming up), then the normal cadence.\n */\n async start(): Promise<void> {\n await this.refreshOnce()\n let attempts = 0\n while (this.#entries.size === 0 && attempts < 4 && !this.#stopped) {\n attempts += 1\n await new Promise((resolve) => setTimeout(resolve, this.#startupRetryMs))\n if (this.#stopped) return\n await this.refreshOnce()\n }\n if (this.#stopped) return\n this.#timer = setInterval(() => {\n void this.refreshOnce()\n }, this.#refreshSeconds * 1000)\n this.#timer.unref?.()\n }\n\n stop(): void {\n this.#stopped = true\n if (this.#timer) {\n clearInterval(this.#timer)\n this.#timer = null\n }\n }\n\n async refreshOnce(): Promise<void> {\n await this.refreshModels()\n if (this.#onRefresh) {\n try {\n this.#onRefresh(this.snapshot(), this.#lastError)\n } catch {\n // observers must never break the refresh loop\n }\n }\n // A refresh can add or drop models: tell the picker to re-read.\n this.#announce()\n }\n\n /**\n * Announce the exposed set when it changed.\n *\n * DSH's browser-side model directory caches one `modelCatalog()` read per\n * Host generation and only re-reads it on `llm/adapters-updated`, so\n * in-memory catalog mutations (hidden models, a refresh that moved the\n * list) are otherwise invisible until restart. Firing the event on every\n * refresh would also work but wakes the whole picker every 5 minutes, so\n * the exposed ids are fingerprinted and only a real change is announced.\n */\n #announce(): void {\n const fingerprint = this.#fingerprint()\n if (fingerprint === this.#announced) return\n this.#announced = fingerprint\n if (!this.#onInvalidate) return\n try {\n this.#onInvalidate()\n } catch {\n // observers must never break the refresh loop\n }\n }\n\n /** Stable fingerprint of the exposed set (ids + their capabilities). */\n #fingerprint(): string {\n const rows = this.list().map((id) => {\n const entry = this.#entryFor(id)\n return [\n id,\n entry?.displayName ?? '',\n entry?.image === true ? 'img' : '-',\n entry?.reasoning === true ? 'think' : '-',\n (entry?.efforts ?? []).join(','),\n String(entry?.contextWindow ?? 0),\n String(entry?.maxTokens ?? 0),\n ].join('|')\n })\n return rows.join('\\n')\n }\n\n /** Whether a gateway API root is configured (empty baseUrl = not configured). */\n get configured(): boolean {\n return this.#baseUrl !== ''\n }\n\n /**\n * Auth header for every catalog fetch. The gateway guards `GET /models` with\n * the same Bearer key as inference, so omitting it is answered 401 \u2014 which\n * the refresh loop then reports as a \"catalog issue\" while the picker keeps\n * serving whatever it already had.\n */\n #authHeaders(): Record<string, string> {\n return this.#apiKey === '' ? {} : { authorization: `Bearer ${this.#apiKey}` }\n }\n\n async refreshModels(): Promise<void> {\n if (!this.configured) {\n this.#lastError = 'magpie gateway API URL or key is not configured \u2014 set both on the Magpie settings page'\n return\n }\n try {\n const entries = await fetchMagpieModels(this.#baseUrl, this.#fetch, { headers: this.#authHeaders() })\n this.#entries = entries\n this.#updatedAt = this.#now()\n this.#lastError = ''\n if (this.#cachePath) await saveModelsCache(this.#cachePath, entries, this.#now()).catch(() => {})\n } catch (err) {\n // Network failure with a cached copy is not fatal: load the cache.\n if (this.#cachePath && this.#entries.size === 0) {\n const cached = await loadModelsCache(this.#cachePath).catch(() => null)\n if (cached && cached.size > 0) {\n this.#entries = cached\n this.#updatedAt = this.#updatedAt === 0 ? this.#now() : this.#updatedAt\n return\n }\n }\n this.#lastError = err instanceof Error ? err.message : String(err)\n }\n }\n\n getEntry(model: string): MagpieModelEntry | undefined {\n return this.#entries.get(model)\n }\n\n /**\n * Replace the settings-page hidden set (models excluded from the picker).\n * Announces: hiding/showing models changes the exposed list, and DSH's\n * picker only re-reads it on `llm/adapters-updated`.\n */\n setHidden(ids: readonly string[]): void {\n const next = new Set(ids)\n if (next.size === this.#hidden.size && [...next].every((id) => this.#hidden.has(id))) return\n this.#hidden = next\n this.#announce()\n }\n\n /** Currently hidden model ids. */\n hidden(): string[] {\n return [...this.#hidden]\n }\n\n /**\n * Drop hidden ids the gateway no longer serves.\n *\n * The hidden set is only meaningful against a live directory: keeping an id\n * that has since left `GET /models` would make the settings page carry rows\n * nobody can act on, and a model that later returns would come back\n * invisibly hidden for no reason the user can see. Called on save, when the\n * directory is the freshest the session has.\n * @param ids - the hidden set as submitted.\n * @returns the subset still worth storing.\n */\n pruneHidden(ids: readonly string[]): string[] {\n if (!this.configured || this.#entries.size === 0) return [...ids]\n return ids.filter((id) => this.#entries.has(id))\n }\n\n isHidden(model: string): boolean {\n return this.#hidden.has(model)\n }\n\n /**\n * Point the refresh loop at another API root or credential (settings page\n * change).\n *\n * The key is part of the identity here, not just the URL: the gateway's\n * `GET /models` is key-guarded, so a key-only edit \u2014 the common case of\n * pasting a fresh key \u2014 must re-read the directory too. When only the key\n * moves the live entries are kept and merely re-fetched; when the root\n * moves they are dropped, because they describe a different gateway.\n * @param baseUrl - the versioned API root ('' = unconfigured).\n * @param apiKey - the gateway credential sent as a Bearer header.\n */\n setEndpoint(baseUrl: string, apiKey: string): void {\n const root = baseUrl.replace(/\\/+$/, '')\n const key = apiKey\n if (root === this.#baseUrl && key === this.#apiKey) return\n const rootChanged = root !== this.#baseUrl\n this.#baseUrl = root\n this.#apiKey = key\n if (rootChanged) {\n // Drop entries from the previous root: the picker must not serve a\n // stale directory, and clearing unconfigured state must empty it.\n this.#entries = new Map()\n this.#updatedAt = 0\n }\n this.#lastError = root === '' ? 'magpie gateway API URL or key is not configured \u2014 set both on the Magpie settings page' : ''\n // The picker must not keep serving the previous endpoint's directory.\n this.#announce()\n }\n\n /** Point the refresh loop at another API root (keeps the current key). */\n setBaseUrl(baseUrl: string): void {\n this.setEndpoint(baseUrl, this.#apiKey)\n }\n\n get baseUrl(): string {\n return this.#baseUrl\n }\n\n get apiKey(): string {\n return this.#apiKey\n }\n\n decision(model: string): { allowed: boolean; source: string; known: boolean } {\n if (!this.configured) return { allowed: false, source: 'unconfigured', known: false }\n if (this.#hidden.has(model)) return { allowed: false, source: 'hidden_by_settings', known: true }\n if (this.#entries.has(model)) return { allowed: true, source: 'gateway', known: true }\n const fallback = staticMagpieModels.find((entry) => entry.id === model)\n if (fallback) return { allowed: true, source: 'static_verified', known: false }\n return { allowed: false, source: this.#entries.size === 0 ? 'gateway_pending' : 'gateway_unknown', known: false }\n }\n\n /** Entry lookup: live map first, static snapshot while configured (never unconfigured). */\n #entryFor(model: string): MagpieModelEntry | undefined {\n if (!this.configured) return undefined\n return this.#entries.get(model) ?? staticMagpieModels.find((candidate) => candidate.id === model)\n }\n\n /**\n * Reasoning capability for one model id (DSH `resolveModel().reasoning`\n * + pi-ai `thinkingLevelMap` source of truth). Ladder-less thinking models\n * (reasoning without an effort list) think with upstream defaults \u2014 see\n * `thinks()` \u2014 and expose no picker.\n */\n reasoningFor(model: string): { efforts: ReasoningEffortChoice[]; defaultEffort: string } | undefined {\n const entry = this.#entryFor(model)\n const efforts = entry?.efforts\n if (!entry?.reasoning) return undefined\n if (efforts !== undefined && efforts.length > 0) {\n const mid = efforts[Math.floor(efforts.length / 2)] ?? 'medium'\n return { efforts: effortsFor(efforts), defaultEffort: efforts.includes('medium') ? 'medium' : mid }\n }\n return undefined\n }\n\n /** Whether the wire model may think (pi-ai `reasoning: true`). */\n thinks(model: string): boolean {\n const entry = this.#entryFor(model)\n return entry?.reasoning ?? false\n }\n\n /** Whether the model accepts image input. */\n supportsImage(model: string): boolean {\n const entry = this.#entryFor(model)\n return entry?.image ?? false\n }\n\n /** Whether one effort id is selectable for the model. */\n supportsEffort(model: string, effort: string): boolean {\n const entry = this.#entryFor(model)\n if (!entry?.reasoning) return false\n if (!entry.efforts || entry.efforts.length === 0) return true // ladder-less: upstream accepts, picker hidden\n return entry.efforts.includes(effort)\n }\n\n requiresResponsesApi(model: string): boolean {\n const entry = this.#entryFor(model)\n if (!entry) return /muse-spark|codex\\/|grok/i.test(model)\n return requiresResponsesApiEntry(entry.nativeEndpoints)\n }\n\n contextWindowFor(model: string): number {\n return this.#entryFor(model)?.contextWindow ?? 262144\n }\n\n maxTokensFor(model: string): number {\n return this.#entryFor(model)?.maxTokens ?? 32768\n }\n\n /** ids exposed to DSH: live gateway list minus hidden (empty while unconfigured). */\n list(): string[] {\n if (!this.configured) return []\n if (this.#entries.size === 0) return staticMagpieModels.map((entry) => entry.id).filter((id) => !this.#hidden.has(id))\n return [...this.#entries.keys()].filter((id) => !this.#hidden.has(id)).sort()\n }\n\n /** Catalog health snapshot (pending/ready/stale + counts). */\n snapshot(): CatalogSnapshot {\n const age = this.#updatedAt === 0 ? Infinity : this.#now() - this.#updatedAt\n const stale = this.#updatedAt !== 0 && age > 10 * 60 * 1000\n return {\n status: this.#updatedAt === 0 ? 'pending' : stale ? 'stale' : 'ready',\n total: this.#entries.size,\n exposed: this.list().length,\n ...(this.#updatedAt !== 0 ? { lastRefresh: new Date(this.#updatedAt).toISOString() } : {}),\n }\n }\n\n get lastError(): string {\n return this.#lastError\n }\n}\n\nasync function withTimeout(\n run: (signal: AbortSignal) => Promise<Response>,\n timeoutMs = FETCH_TIMEOUT_MS,\n): Promise<Response> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await run(controller.signal)\n } catch (error) {\n if (controller.signal.aborted) throw new Error(`models endpoint timed out after ${timeoutMs}ms`)\n throw error\n } finally {\n clearTimeout(timer)\n }\n}\n\n/**\n * Fetch the live gateway model list.\n *\n * `baseUrl` is the versioned API root (`http://127.0.0.1:3425/v1`) exactly as the user\n * configured it; only the resource is appended. Never synthesize a version\n * here \u2014 a gateway on `/v2` would otherwise be asked for `/v2/v1/models`.\n */\nexport async function fetchMagpieModels(\n baseUrl: string,\n fetchImpl: typeof fetch,\n options: { timeoutMs?: number; headers?: Record<string, string> } = {},\n): Promise<Map<string, MagpieModelEntry>> {\n const response = await withTimeout(\n (signal) =>\n fetchImpl(`${baseUrl.replace(/\\/+$/, '')}/models`, {\n headers: { accept: 'application/json', ...(options.headers ?? {}) },\n signal,\n }),\n options.timeoutMs ?? FETCH_TIMEOUT_MS,\n )\n if (!response.ok) throw new Error(`models endpoint returned HTTP ${response.status}`)\n const payload = (await response.json()) as unknown\n const models = decodeMagpieModels(payload)\n if (models.size === 0) throw new Error('models endpoint returned an empty list')\n return models\n}\n\ninterface ModelsCache {\n updatedAt: number\n entries: MagpieModelEntry[]\n}\n\nasync function saveModelsCache(path: string, entries: Map<string, MagpieModelEntry>, now: number): Promise<void> {\n const cache: ModelsCache = { updatedAt: now, entries: [...entries.values()] }\n const tmp = `${path}.${process.pid}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, JSON.stringify(cache), 'utf8')\n await rm(path, { force: true })\n await rename(tmp, path)\n}\n\nasync function loadModelsCache(path: string): Promise<Map<string, MagpieModelEntry>> {\n const raw = JSON.parse(await readFile(path, 'utf8')) as ModelsCache\n if (Date.now() - raw.updatedAt > 7 * 24 * 60 * 60 * 1000) {\n throw new Error('gateway cache too old')\n }\n return new Map(raw.entries.map((entry) => [entry.id, entry]))\n}\n\n/** Default cache location next to the plugin data dir (index.ts convention). */\nexport function defaultCachePath(dataDir: string): string {\n return join(dataDir, 'magpie-models.json')\n}\n", "import { createProvider, type Api, type Context, type Model } from '@earendil-works/pi-ai'\nimport * as openaiCompletions from '@earendil-works/pi-ai/api/openai-completions'\nimport * as openaiResponses from '@earendil-works/pi-ai/api/openai-responses'\n\nimport {\n ModelCatalog,\n modelApiForEntry,\n thinkingLevelMapFor,\n} from './catalog.ts'\nimport { toStreamChunks, type HarnessChunk, type PiEvent } from './events.ts'\nimport {\n contentHasImage,\n toPiContext,\n toPiContextWithImages,\n type AttachmentStore,\n type HarnessGenerateOptions,\n} from './messages.ts'\nimport { withStallTimeout } from './watchdog.ts'\n\n/**\n * Magpie LAN gateway adapter: registers as a DSH LlmAdapter and streams\n * directly from the configured API root (e.g. `\u2026/v1`) \u2014 chat completions for most models plus the\n * Responses API for responses-only models (Muse Spark / Codex / Grok lanes).\n *\n * Adapter contract: dsh-llm LlmAdapter (providerInfo/providerRetryPolicy/\n * imageRequestPricing/listModels/resolveModel/prepareCall/stream) \u2014\n * structural, no host import.\n *\n * Images resolve through the harness attachment service; thinking levels come\n * from the gateway catalog and forward verbatim (including the `none`/`ultra`\n * extensions pi-ai does not model).\n */\n\n/** Structural pi-ai provider surface the adapter consumes. */\nexport interface PiProviderLike {\n stream(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>\n streamSimple(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>\n}\n\n/** Live endpoint read per request so the settings page applies without restart. */\nexport interface RuntimeEndpoint {\n /** Versioned API root, e.g. `http://127.0.0.1:3425/v1` (no trailing slash). */\n baseUrl(): string\n /** Bearer key sent to the gateway. */\n apiKey(): string\n}\n\nexport const PROVIDER_ID = 'dsh-magpie-connect'\n\n/** Default picker label for the default route id. */\nexport const DEFAULT_DISPLAY_NAME = 'Magpie'\n\nexport interface MagpieModelInfo {\n id: string\n name: string\n contextWindow: number\n maxTokens: number\n}\n\nexport interface CatalogLike {\n list(): string[]\n decision(model: string): { allowed: boolean; source: string; known: boolean }\n getEntry?(model: string): { nativeEndpoints: string[] } | undefined\n reasoningFor?(model: string): { efforts: Array<{ id: string; name: string; description?: string }>; defaultEffort: string } | undefined\n thinks?(model: string): boolean\n supportsImage?(model: string): boolean\n supportsEffort?(model: string, effort: string): boolean\n requiresResponsesApi?(model: string): boolean\n contextWindowFor?(model: string): number\n maxTokensFor?(model: string): number\n}\n\nconst DEFAULT_CONTEXT_WINDOW = 262144\nconst DEFAULT_MAX_TOKENS = 32768\n\n/** Credential sent to the LAN gateway (it needs none; pi-ai requires a value). */\nconst DEFAULT_API_KEY = 'not-needed'\n\n/** Connection-setup retries (429/5xx with backoff, interruptible by abort). */\nexport const DEFAULT_MAX_RETRIES = 2\n/** Overall SDK request cap (replaces the OpenAI SDK 10 min default). */\nexport const DEFAULT_TIMEOUT_MS = 300_000\n/** Max wait for the first upstream event (queueing happens here). */\nexport const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 90_000\n/** Max silence between upstream events once streaming. */\nexport const DEFAULT_IDLE_TIMEOUT_MS = 60_000\n\nconst STRENGTH_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const\n\n/**\n * Plan the wire `reasoning_effort` for one request. Returns the effort to\n * send via `provider.stream`, or undefined to omit the field (gateway\n * default applies).\n *\n * - `off` aliases `none` (explicitly disable thinking).\n * - `none` sends only when the model lists it; otherwise omitted.\n * - `ultra` sends when listed; otherwise degrades to the highest listed\n * strength level (`max` \u2192 \u2026 \u2192 `minimal`).\n * - Known strength levels send verbatim when listed or when the model thinks\n * without a ladder (gateway decides); otherwise clamped up-then-down to\n * the nearest listed level so a stale remembered value never 400s.\n * - Unknown values and non-reasoning models omit.\n */\nexport function planReasoningEffort(\n requested: unknown,\n model: string,\n catalog: Pick<CatalogLike, 'thinks' | 'supportsEffort' | 'reasoningFor'>,\n): string | undefined {\n if (typeof requested !== 'string') return undefined\n const level = requested.trim().toLowerCase()\n if (level === '') return undefined\n const thinks = catalog.thinks?.(model) ?? false\n if (!thinks) return undefined\n const normalized = level === 'off' ? 'none' : level\n const supports = (effort: string): boolean => catalog.supportsEffort?.(model, effort) ?? false\n const ladder = catalog.reasoningFor?.(model)?.efforts.map((effort) => effort.id)\n const ladderLess = ladder === undefined\n\n if (normalized === 'none') {\n return supports('none') ? 'none' : undefined\n }\n if (!(STRENGTH_ORDER as readonly string[]).includes(normalized)) return undefined\n if (supports(normalized) || ladderLess) return normalized\n // Clamp a stale/foreign level up-then-down (pi-ai clamp order).\n const order = STRENGTH_ORDER as readonly string[]\n const index = order.indexOf(normalized as (typeof STRENGTH_ORDER)[number])\n if (index !== -1) {\n for (let i = index + 1; i < order.length; i++) {\n const candidate = order[i] as string\n if (supports(candidate)) return candidate\n }\n for (let i = index - 1; i >= 0; i--) {\n const candidate = order[i] as string\n if (supports(candidate)) return candidate\n }\n }\n return undefined\n}\n\nfunction toPiModel(\n providerId: string,\n baseUrl: string,\n id: string,\n catalog: CatalogLike,\n contextWindow = DEFAULT_CONTEXT_WINDOW,\n maxTokens = DEFAULT_MAX_TOKENS,\n): Model<Api> {\n const endpoints = catalog.getEntry?.(id)?.nativeEndpoints ?? []\n const api = modelApiForEntry(endpoints)\n const entryThinks = catalog.thinks?.(id) ?? api === 'openai-responses'\n const ladder = catalog.reasoningFor?.(id)\n const ladderIds = ladder?.efforts.map((effort) => effort.id)\n const resolvedContext = catalog.contextWindowFor?.(id) ?? contextWindow\n const resolvedMax = catalog.maxTokensFor?.(id) ?? maxTokens\n if (api === 'openai-responses') {\n return {\n id,\n name: id,\n api,\n provider: providerId,\n baseUrl,\n reasoning: true,\n thinkingLevelMap: thinkingLevelMapFor(ladderIds),\n input: catalog.supportsImage?.(id) ?? true ? ['text', 'image'] : ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: resolvedContext,\n maxTokens: resolvedMax,\n }\n }\n const wire: Model<Api> = {\n id,\n name: id,\n api,\n provider: providerId,\n baseUrl,\n reasoning: entryThinks,\n input: catalog.supportsImage?.(id) ?? false ? ['text', 'image'] : ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: resolvedContext,\n maxTokens: resolvedMax,\n }\n if (entryThinks) wire.thinkingLevelMap = thinkingLevelMapFor(ladderIds)\n return wire\n}\n\nexport class MagpieAdapter {\n readonly #catalog: CatalogLike\n readonly #provider: PiProviderLike\n readonly #providerId: string\n readonly #displayName: string\n /** Static fallback API root; the live settings page overrides per request. */\n readonly #fallbackBaseUrl: string\n readonly #fallbackApiKey: string\n readonly #runtime?: RuntimeEndpoint\n readonly #maxRetries: number\n readonly #timeoutMs: number\n readonly #firstEventTimeoutMs: number\n readonly #idleTimeoutMs: number\n readonly #resolveAttachments?: () => AttachmentStore | undefined\n\n constructor(\n catalog: CatalogLike,\n options: {\n magpieBaseUrl?: string\n baseUrl?: string\n providerOverride?: unknown\n /** Route id registered into DSH (model `provider` field + pi-ai provider tag). */\n providerId?: string\n /** Display name reported via providerInfo (model picker grouping label). */\n displayName?: string\n /** Gateway credential (LAN needs none). */\n apiKey?: string\n /**\n * Live endpoint read per request (settings page). Falls back to the\n * static `magpieBaseUrl`/`apiKey` options when absent.\n */\n runtime?: RuntimeEndpoint\n /** Connection-setup retries for 429/5xx (default 2). */\n maxRetries?: number\n /** Overall SDK request cap in ms (default 300000). */\n timeoutMs?: number\n /** Stall watchdog: max wait for the first upstream event in ms (default 90000; <=0 disables). */\n firstEventTimeoutMs?: number\n /** Stall watchdog: max silence between upstream events in ms (default 60000; <=0 disables). */\n idleTimeoutMs?: number\n /** Harness attachment service for image bytes (wired in index.ts). */\n resolveAttachments?: () => AttachmentStore | undefined\n } = {},\n ) {\n this.#catalog = catalog\n this.#providerId = options.providerId ?? PROVIDER_ID\n this.#displayName = options.displayName ?? (options.providerId ?? DEFAULT_DISPLAY_NAME)\n // The configured value is already the versioned API root (`\u2026/v1`), which is\n // exactly what pi-ai's `baseUrl` expects: it appends `/chat/completions`\n // and `/responses` itself. Synthesizing a version here would double it and\n // would hard-code `/v1` against a gateway that later moves to `/v2`.\n this.#fallbackBaseUrl = (options.magpieBaseUrl ?? options.baseUrl ?? '').replace(/\\/+$/, '')\n this.#fallbackApiKey = options.apiKey ?? DEFAULT_API_KEY\n this.#runtime = options.runtime\n this.#maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.#firstEventTimeoutMs = options.firstEventTimeoutMs ?? DEFAULT_FIRST_EVENT_TIMEOUT_MS\n this.#idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS\n this.#resolveAttachments = options.resolveAttachments\n if (options.providerOverride !== undefined) {\n this.#provider = options.providerOverride as never\n return\n }\n this.#provider = createProvider<Api>({\n id: this.#providerId,\n name: this.#displayName,\n baseUrl: this.#fallbackBaseUrl,\n auth: {\n apiKey: {\n name: 'Magpie LAN gateway',\n resolve: async () => ({ auth: { apiKey: this.#runtime?.apiKey() ?? this.#fallbackApiKey } }),\n },\n },\n models: [],\n // Mixed-API provider: pi-ai dispatches on `model.api`, so completions\n // models ride /v1/chat/completions while responses-only models ride\n // /v1/responses through the same provider instance.\n api: {\n 'openai-completions': openaiCompletions,\n 'openai-responses': openaiResponses,\n },\n }) as unknown as PiProviderLike\n }\n\n providerInfo(provider: string): { id: string; name: string } {\n return { id: provider, name: this.#displayName }\n }\n\n /**\n * dsh-llm calls this unconditionally at registration.\n * undefined = the host default retry policy.\n */\n providerRetryPolicy(_provider: string): undefined {\n return undefined\n }\n\n /**\n * Per-route visual-token pricing for the token meter (`/compact` calls\n * this unconditionally via `ctx.llm.imageRequestPricing`). The gateway\n * charges no separate visual tokens (pi-ai cost is 0), so like the host\n * base-class default we declare no per-route pricing.\n */\n imageRequestPricing(_provider: string, _model: string): undefined {\n return undefined\n }\n\n /** Advisory catalog for the DSH model picker (deduped; dsh-llm rejects duplicates). */\n listModels(provider: string): Array<{ provider: string; id: string; name: string; inputModalities: string[] }> {\n const seen = new Set<string>()\n const models: Array<{ provider: string; id: string; name: string; inputModalities: string[] }> = []\n for (const id of this.#catalog.list()) {\n if (seen.has(id)) continue\n seen.add(id)\n const image = this.#catalog.supportsImage?.(id) ?? false\n models.push({ provider, id, name: id, inputModalities: image ? ['text', 'image'] : ['text'] })\n }\n return models\n }\n\n resolveModel(\n provider: string,\n model: string,\n ): {\n provider: string\n id: string\n name: string\n inputModalities: string[]\n context: { contextWindow: number }\n defaultMaxTokens: number\n reasoning?: {\n efforts: ReadonlyArray<{ id: string; name: string; description?: string }>\n defaultEffort?: string\n }\n } {\n // DSH capability seam: exposing efforts lights up the session model\n // selector's reasoning picker; ids flow back as\n // GenerateOptions.reasoningEffort and are forwarded verbatim by\n // planReasoningEffort (gateway extensions `none`/`ultra` included).\n // No `off` entry: omission keeps the gateway default (= defaultEffort).\n const image = this.#catalog.supportsImage?.(model) ?? this.#catalog.requiresResponsesApi?.(model) ?? false\n const ladder = this.#catalog.reasoningFor?.(model)\n return {\n provider,\n id: model,\n name: model,\n inputModalities: image ? ['text', 'image'] : ['text'],\n context: { contextWindow: this.#catalog.contextWindowFor?.(model) ?? DEFAULT_CONTEXT_WINDOW },\n defaultMaxTokens: this.#catalog.maxTokensFor?.(model) ?? DEFAULT_MAX_TOKENS,\n ...(ladder !== undefined ? { reasoning: ladder } : {}),\n }\n }\n\n async prepareCall(\n provider: string,\n model: string,\n _signal?: AbortSignal,\n ): Promise<{\n model: ReturnType<MagpieAdapter['resolveModel']>\n stream: (options: HarnessGenerateOptions) => AsyncGenerator<HarnessChunk>\n }> {\n return {\n model: this.resolveModel(provider, model),\n stream: (options) => this.stream(options),\n }\n }\n\n /**\n * Fail fast when no gateway API URL is configured (picker stays empty too).\n * @returns the versioned API root, trailing slash stripped.\n */\n #requireBaseUrl(): string {\n const raw = this.#runtime?.baseUrl() ?? this.#fallbackBaseUrl\n const baseUrl = raw.replace(/\\/+$/, '')\n if (baseUrl === '') {\n throw new Error('dsh-magpie-connect: Magpie gateway API URL or key is not configured \u2014 open Settings \u2192 Magpie and fill in both')\n }\n return baseUrl\n }\n\n /** Effective bearer key for this request. */\n #wireApiKey(): string {\n return this.#runtime?.apiKey() ?? this.#fallbackApiKey\n }\n\n /**\n * Stream one chat turn from the Magpie gateway: a single pi-ai stream,\n * translated to harness chunks verbatim. Upstream failures (rate limit,\n * auth, timeout, transport) arrive as classified finish reasons, and\n * turn-level retries stay owned by DSH.\n *\n * A stall watchdog races every upstream event against a timer: on expiry\n * the upstream is aborted and the turn ends fast with a TIMEOUT error\n * instead of hanging to the SDK/harness timeout.\n */\n async *stream(options: HarnessGenerateOptions): AsyncGenerator<HarnessChunk> {\n const baseUrl = this.#requireBaseUrl()\n const endpoints = this.#catalog.getEntry?.(options.model)?.nativeEndpoints ?? []\n const hasImage = contentHasImage(options.messages)\n if (hasImage && !(this.#catalog.supportsImage?.(options.model) ?? false)) {\n throw new Error(`dsh-magpie-connect: model \"${options.model}\" does not accept image input`)\n }\n const context = hasImage\n ? await this.#imageContext(options, endpoints)\n : toPiContext(options, endpoints)\n const model = toPiModel(this.#providerId, baseUrl, options.model, this.#catalog)\n // Linked controller: the watchdog aborts the upstream on stall, while a\n // harness abort (user stop / host timeout) still propagates through.\n const controller = new AbortController()\n const harnessSignal = options.signal\n const onHarnessAbort = (): void => controller.abort()\n if (harnessSignal) {\n if (harnessSignal.aborted) controller.abort()\n else harnessSignal.addEventListener('abort', onHarnessAbort, { once: true })\n }\n try {\n const events = withStallTimeout(\n this.#eventsFor(options, context, model, controller.signal) as AsyncIterable<PiEvent>,\n { firstEventTimeoutMs: this.#firstEventTimeoutMs, idleTimeoutMs: this.#idleTimeoutMs },\n { model: options.model, onTimeout: () => controller.abort() },\n )\n yield* toStreamChunks(events, model.contextWindow)\n } finally {\n harnessSignal?.removeEventListener('abort', onHarnessAbort)\n }\n }\n\n async #imageContext(options: HarnessGenerateOptions, endpoints: readonly string[]): Promise<ReturnType<typeof toPiContext>> {\n const attachments = this.#resolveAttachments?.()\n if (!attachments) {\n throw new Error('dsh-magpie-connect: image input requires the attachment service')\n }\n return toPiContextWithImages(options, attachments, endpoints)\n }\n\n #eventsFor(\n options: HarnessGenerateOptions,\n context: ReturnType<typeof toPiContext>,\n model: ReturnType<typeof toPiModel>,\n signal: AbortSignal,\n ): unknown {\n // Single dispatch through `provider.stream` with our own clamped effort:\n // gateway extensions (`none`/`ultra`) ride verbatim, known levels ride\n // the ladder, and omission keeps the gateway default.\n const reasoningEffort = planReasoningEffort(options.reasoningEffort, options.model, this.#catalog)\n return this.#provider.stream(model, context as unknown as Context, {\n apiKey: this.#wireApiKey(),\n signal,\n maxRetries: this.#maxRetries,\n timeoutMs: this.#timeoutMs,\n temperature: options.temperature,\n maxTokens: options.maxTokens ?? model.maxTokens,\n ...(reasoningEffort !== undefined ? { reasoningEffort } : {}),\n })\n }\n\n /** Expose the live catalog snapshot for diagnostics. */\n catalogStatus(): { total: number; exposed: number } {\n const list = this.#catalog.list()\n return { total: list.length, exposed: list.length }\n }\n\n decisionFor(model: string): { allowed: boolean; source: string } {\n const decision = this.#catalog.decision(model)\n return { allowed: decision.allowed, source: decision.source }\n }\n}\n\n/** Build the adapter over a live catalog. */\nexport function createMagpieAdapter(catalog: ModelCatalog): MagpieAdapter {\n return new MagpieAdapter(catalog)\n}\n", "/**\n * pi-ai AssistantMessageEvent -> harness StreamChunks.\n * The chunk stream must end with `usage` then `finish`.\n */\n\nexport type HarnessChunk =\n | { type: 'block-start'; index: number; blockType: 'text' | 'reasoning' | 'tool-call' }\n | { type: 'text-delta'; index: number; text: string }\n | { type: 'block-end'; index: number; block: { type: 'text'; text: string } | { type: 'reasoning'; text: string } | { type: 'tool-call'; id: string; name: string; arguments: string } }\n | { type: 'reasoning-delta'; index: number; text: string }\n | { type: 'tool-call-delta'; index: number; id: string; name?: string; argumentsDelta: string }\n | { type: 'usage'; usage: { inputTokens: number; outputTokens: number; cacheReadTokens?: number; cacheWriteTokens?: number } }\n | { type: 'finish'; reason: FinishReason; replayState?: unknown }\n\nexport type FinishReason =\n | { kind: 'stop' }\n | { kind: 'max-tokens' }\n | { kind: 'tool-calls' }\n | { kind: 'aborted'; failure: { message: string; code: string } }\n | { kind: 'error'; failure: { message: string; code: string } }\n\n/** pi-ai AssistantMessageEvent vocabulary (subset we consume). */\nexport type PiEvent =\n | { type: 'start'; partial: PiAssistantPartial }\n | { type: 'text_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'text_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'text_end'; contentIndex: number; content: string; partial: PiAssistantPartial }\n | { type: 'thinking_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'thinking_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'thinking_end'; contentIndex: number; content: string; partial: PiAssistantPartial }\n | { type: 'toolcall_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'toolcall_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'toolcall_end'; contentIndex: number; toolCall: { id: string; name: string; arguments: Record<string, unknown> }; partial: PiAssistantPartial }\n | { type: 'done'; message: PiDoneMessage }\n | { type: 'error'; error: PiDoneMessage }\n\nexport interface PiAssistantPartial {\n content: Array<{ type: string; id?: string; name?: string; [key: string]: unknown }>\n [key: string]: unknown\n}\n\n/** pi-ai AssistantMessage content block (done/error messages carry these). */\nexport type PiAssistantBlock = { type: string; [key: string]: unknown }\n\nexport interface PiDoneMessage {\n api: string\n provider: string\n model: string\n responseModel?: string\n responseId?: string\n content: PiAssistantBlock[]\n usage: { input: number; output: number; cacheRead: number; cacheWrite: number; totalTokens: number }\n stopReason: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'\n errorMessage?: string\n [key: string]: unknown\n}\n\nconst CONTEXT_WINDOW_EXCEEDED = 'CONTEXT_WINDOW_EXCEEDED'\nconst EMPTY_RESPONSE = 'EMPTY_RESPONSE'\nconst QUOTA_EXCEEDED = 'QUOTA_EXCEEDED'\n\nfunction classifyError(text: string): string {\n if (/\\b(?:401|403)\\b/.test(text)) return 'AUTH'\n if (/insufficient|quota|billing/i.test(text)) return QUOTA_EXCEEDED\n if (/\\b429\\b|rate.?limit/i.test(text)) return 'RATE_LIMIT'\n if (/\\b413\\b|payload too large|request body too large/i.test(text)) return 'INVALID_REQUEST'\n if (/\\b400\\b|invalid.?request/i.test(text)) return 'INVALID_REQUEST'\n if (/\\b5\\d\\d\\b/.test(text)) return 'SERVER'\n if (/\\btime(?:d)?\\s*out\\b|timeout/i.test(text)) return 'TIMEOUT'\n if (/\\b(?:network|connection|socket|fetch)\\b|\\bECONN[A-Z]+\\b|terminated|premature close/i.test(text)) return 'TRANSPORT'\n return 'UPSTREAM'\n}\n\nfunction isContextOverflow(message: PiDoneMessage, contextWindow: number): boolean {\n // pi-ai isContextOverflow equivalent: error-free finish whose input usage\n // alone exceeds the resolved window.\n return message.stopReason === 'stop' && message.usage.input > contextWindow\n}\n\n/** Map a pi-ai terminal message to a harness finish reason. */\nfunction mapStopReason(message: PiDoneMessage, contextWindow: number): FinishReason {\n if (isContextOverflow(message, contextWindow) || (message.stopReason === 'error' && message.errorMessage !== undefined && /context/i.test(message.errorMessage) && /exceed|window|length|token/i.test(message.errorMessage))) {\n return {\n kind: 'error',\n failure: {\n message: message.errorMessage ?? `pi-ai detected context overflow for model \"${message.model}\"`,\n code: CONTEXT_WINDOW_EXCEEDED,\n },\n }\n }\n switch (message.stopReason) {\n case 'stop':\n if (message.content.length === 0) {\n return {\n kind: 'error',\n failure: { message: `model \"${message.model}\" returned a completed response with no content`, code: EMPTY_RESPONSE },\n }\n }\n return { kind: 'stop' }\n case 'length':\n return { kind: 'max-tokens' }\n case 'toolUse':\n return { kind: 'tool-calls' }\n case 'aborted':\n return { kind: 'aborted', failure: { message: message.errorMessage ?? 'pi-ai stream aborted', code: 'ABORTED' } }\n case 'error':\n return {\n kind: 'error',\n failure: { message: message.errorMessage ?? 'pi-ai stream error', code: classifyError(message.errorMessage ?? '') },\n }\n }\n}\n\nfunction mapUsage(usage: PiDoneMessage['usage']): Extract<HarnessChunk, { type: 'usage' }>['usage'] {\n return {\n inputTokens: usage.input,\n outputTokens: usage.output,\n ...(usage.cacheRead > 0 ? { cacheReadTokens: usage.cacheRead } : {}),\n ...(usage.cacheWrite > 0 ? { cacheWriteTokens: usage.cacheWrite } : {}),\n }\n}\n\n/**\n * Translate one pi-ai event stream into harness chunks. pi-ai never throws\n * mid-stream: failures arrive as `error` events and become error/aborted\n * finish chunks.\n */\nexport async function* toStreamChunks(events: AsyncIterable<PiEvent>, contextWindow: number): AsyncGenerator<HarnessChunk> {\n const toolIds = new Map<number, { id: string; name: string }>()\n for await (const event of events) {\n switch (event.type) {\n case 'start':\n break\n case 'text_start':\n yield { type: 'block-start', index: event.contentIndex, blockType: 'text' }\n break\n case 'text_delta':\n yield { type: 'text-delta', index: event.contentIndex, text: event.delta }\n break\n case 'text_end':\n yield { type: 'block-end', index: event.contentIndex, block: { type: 'text', text: event.content } }\n break\n case 'thinking_start':\n yield { type: 'block-start', index: event.contentIndex, blockType: 'reasoning' }\n break\n case 'thinking_delta':\n yield { type: 'reasoning-delta', index: event.contentIndex, text: event.delta }\n break\n case 'thinking_end':\n yield { type: 'block-end', index: event.contentIndex, block: { type: 'reasoning', text: event.content } }\n break\n case 'toolcall_start': {\n const partial = event.partial.content[event.contentIndex]\n const id = partial?.type === 'toolCall' ? (partial.id ?? '') : ''\n const name = partial?.type === 'toolCall' ? (partial.name ?? '') : ''\n toolIds.set(event.contentIndex, { id, name })\n yield { type: 'block-start', index: event.contentIndex, blockType: 'tool-call' }\n break\n }\n case 'toolcall_delta': {\n const known = toolIds.get(event.contentIndex)\n yield {\n type: 'tool-call-delta',\n index: event.contentIndex,\n id: known?.id ?? '',\n ...(known?.name !== undefined && known.name.length > 0 ? { name: known.name } : {}),\n argumentsDelta: event.delta,\n }\n break\n }\n case 'toolcall_end':\n yield {\n type: 'block-end',\n index: event.contentIndex,\n block: {\n type: 'tool-call',\n id: event.toolCall.id,\n name: event.toolCall.name,\n arguments: JSON.stringify(event.toolCall.arguments),\n },\n }\n break\n case 'done':\n yield { type: 'usage', usage: mapUsage(event.message.usage) }\n yield { type: 'finish', reason: mapStopReason(event.message, contextWindow) }\n return\n case 'error':\n yield { type: 'usage', usage: mapUsage(event.error.usage) }\n yield { type: 'finish', reason: mapStopReason(event.error, contextWindow) }\n return\n }\n }\n throw new Error('dsh-magpie-connect: pi-ai event stream ended without done/error')\n}\n", "/**\n * Harness GenerateOptions -> pi-ai Context conversion, with image support.\n *\n * Text-only requests convert synchronously (`toPiContext`); requests carrying\n * durable image blocks resolve bytes through the harness attachment service\n * (`toPiContextWithImages`, async). pi-ai then encodes images as OpenAI\n * `image_url` (completions) or `input_image` (responses) parts.\n */\n\nimport { modelApiForEntry } from './catalog.ts'\n\nexport interface ImageAttachmentRef {\n attachmentId: string\n mediaType: string\n bytes: number\n width: number\n height: number\n name?: string\n}\n\nexport interface FileAttachmentRef {\n attachmentId: string\n name: string\n bytes: number\n}\n\nexport interface HarnessTool {\n name: string\n description: string\n parameters: unknown\n}\n\nexport type HarnessBlock =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool-call'; id: string; name: string; arguments: string }\n | { type: 'image'; attachment: ImageAttachmentRef; offloaded?: true; [key: string]: unknown }\n | { type: 'file'; attachment: FileAttachmentRef; [key: string]: unknown }\n | { type: 'tool-result'; toolCallId: string; content: HarnessBlock[]; isError?: boolean; [key: string]: unknown }\n | { type: string; [key: string]: unknown }\n\nexport interface HarnessMessage {\n role: 'system' | 'user' | 'assistant' | 'tool' | 'developer'\n content: HarnessBlock[]\n source?: { kind: string; provider?: string; model?: string; callId?: string; [key: string]: unknown }\n toolCallId?: string\n isError?: boolean\n [key: string]: unknown\n}\n\nexport interface HarnessGenerateOptions {\n provider: string\n model: string\n messages: HarnessMessage[]\n system?: string\n tools?: HarnessTool[]\n maxTokens?: number\n temperature?: number\n reasoningEffort?: string\n signal?: AbortSignal\n [key: string]: unknown\n}\n\n/** pi-ai message vocabulary (subset we emit). */\nexport type PiMessage =\n | { role: 'user'; content: string | PiContentBlock[]; timestamp: number }\n | {\n role: 'assistant'\n content: PiAssistantBlock[]\n api: 'openai-completions' | 'openai-responses'\n provider: string\n model: string\n usage: PiUsage\n stopReason: 'stop' | 'toolUse'\n timestamp: number\n }\n | { role: 'toolResult'; toolCallId: string; toolName: string; content: PiContentBlock[]; isError: boolean; timestamp: number }\n\nexport type PiAssistantBlock =\n | { type: 'text'; text: string }\n | { type: 'thinking'; thinking: string }\n | { type: 'toolCall'; id: string; name: string; arguments: Record<string, unknown> }\n\nexport type PiContentBlock = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n\nexport interface PiUsage {\n input: number\n output: number\n cacheRead: number\n cacheWrite: number\n totalTokens: number\n cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number }\n}\n\nexport interface PiTool {\n name: string\n description: string\n parameters: unknown\n}\n\nexport interface PiContext {\n systemPrompt?: string\n messages: PiMessage[]\n tools?: PiTool[]\n}\n\n/** Attachment service surface we consume (structural subset of dsh-attachment). */\nexport interface AttachmentStore {\n readImageRequest(\n ref: ImageAttachmentRef,\n target: { width: number; height: number; maxBytes: number },\n signal?: AbortSignal,\n ): Promise<{ data: Uint8Array; mediaType: string; bytes: number; width: number; height: number }>\n}\n\nexport interface RequestImageVersion {\n data: Uint8Array\n mediaType: string\n bytes: number\n width: number\n height: number\n}\n\nconst REQUEST_MAX_PIXELS = 2048 * 2048\nconst REQUEST_MAX_BYTES = 1024 * 1024\n\nexport function zeroUsage(): PiUsage {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n }\n}\n\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (typeof raw !== 'string' || raw.length === 0) return {}\n try {\n const parsed = JSON.parse(raw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : { value: parsed }\n } catch {\n return { raw }\n }\n}\n\n/** True when any message carries a retained (non-offloaded) image block. */\nexport function contentHasImage(messages: readonly HarnessMessage[]): boolean {\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'image' && (block as { offloaded?: unknown }).offloaded !== true) return true\n }\n }\n return false\n}\n\n/** Request target for one attachment: fit within the pixel budget, cap bytes. */\nexport function requestImageTarget(ref: ImageAttachmentRef): { width: number; height: number; maxBytes: number } {\n const pixels = ref.width * ref.height\n if (!(pixels > REQUEST_MAX_PIXELS) || !(ref.width > 0 && ref.height > 0)) {\n return { width: ref.width, height: ref.height, maxBytes: REQUEST_MAX_BYTES }\n }\n const scale = Math.sqrt(REQUEST_MAX_PIXELS / pixels)\n return { width: Math.max(1, Math.floor(ref.width * scale)), height: Math.max(1, Math.floor(ref.height * scale)), maxBytes: REQUEST_MAX_BYTES }\n}\n\nfunction imageHandleText(ref: ImageAttachmentRef, version: RequestImageVersion): string {\n const name = ref.name ?? `attachment ${String(ref.attachmentId).slice(0, 12)}`\n return `Image ${name}; request preview ${version.width}x${version.height}px.`\n}\n\nfunction offloadedImageText(ref: ImageAttachmentRef): string {\n const name = ref.name ?? `attachment ${String(ref.attachmentId).slice(0, 12)}`\n return `[image omitted (offloaded); ${name}]`\n}\n\nfunction toBase64(data: Uint8Array): string {\n return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64')\n}\n\n/**\n * Resolve every retained image to request bytes. Offloaded occurrences need\n * no bytes (placeholder text only) and are skipped here.\n */\nexport async function prepareRequestImages(\n messages: readonly HarnessMessage[],\n attachments: AttachmentStore,\n signal?: AbortSignal,\n): Promise<Map<string, RequestImageVersion>> {\n const refs = new Map<string, ImageAttachmentRef>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type !== 'image') continue\n const image = block as Extract<HarnessBlock, { type: 'image' }>\n if (image.offloaded === true) continue\n if (!refs.has(image.attachment.attachmentId)) refs.set(image.attachment.attachmentId, image.attachment)\n }\n }\n const versions = new Map<string, RequestImageVersion>()\n await Promise.all(\n [...refs.values()].map(async (ref) => {\n const prepared = await attachments.readImageRequest(ref, requestImageTarget(ref), signal)\n versions.set(ref.attachmentId, {\n data: prepared.data,\n mediaType: prepared.mediaType,\n bytes: prepared.bytes,\n width: prepared.width,\n height: prepared.height,\n })\n }),\n )\n return versions\n}\n\n/** User/tool-result content with images resolved to handle text + bytes. */\nexport function userContentWithImages(\n blocks: HarnessBlock[],\n requestImages: Map<string, RequestImageVersion>,\n): string | PiContentBlock[] {\n const content: PiContentBlock[] = []\n for (const block of blocks) {\n switch (block.type) {\n case 'text':\n if ((block as { text: string }).text.length > 0) content.push({ type: 'text', text: (block as { text: string }).text })\n break\n case 'image': {\n const image = block as Extract<HarnessBlock, { type: 'image' }>\n if (image.offloaded === true) {\n content.push({ type: 'text', text: offloadedImageText(image.attachment) })\n break\n }\n const version = requestImages.get(image.attachment.attachmentId)\n if (!version) {\n content.push({ type: 'text', text: offloadedImageText(image.attachment) })\n break\n }\n content.push({ type: 'text', text: imageHandleText(image.attachment, version) })\n content.push({ type: 'image', data: toBase64(version.data), mimeType: version.mediaType })\n break\n }\n case 'file': {\n const file = block as Extract<HarnessBlock, { type: 'file' }>\n content.push({ type: 'text', text: `[file: ${file.attachment.name} (${file.attachment.bytes} bytes)]` })\n break\n }\n case 'tool-result':\n content.push({ type: 'text', text: toolResultText((block as { content: HarnessBlock[] }).content) || '(no output)' })\n break\n default:\n break\n }\n }\n if (content.every((block) => block.type === 'text')) return content.map((block) => (block as { text: string }).text).join('')\n return content\n}\n\nfunction toPiAssistant(\n message: HarnessMessage,\n providerId: string,\n api: 'openai-completions' | 'openai-responses' = 'openai-completions',\n): Extract<PiMessage, { role: 'assistant' }> {\n const content: PiAssistantBlock[] = []\n for (const block of message.content) {\n switch (block.type) {\n case 'text':\n content.push({ type: 'text', text: (block as { text: string }).text })\n break\n case 'reasoning':\n content.push({ type: 'thinking', thinking: (block as { text: string }).text })\n break\n case 'tool-call': {\n const call = block as { id: string; name: string; arguments: string }\n content.push({ type: 'toolCall', id: call.id, name: call.name, arguments: parseArguments(call.arguments) })\n break\n }\n case 'image':\n throw new Error('dsh-magpie-connect: assistant image output cannot be replayed to the model')\n default:\n break\n }\n }\n const source = message.source\n return {\n role: 'assistant',\n content,\n api,\n provider: source?.kind === 'model' && typeof source.provider === 'string' ? source.provider : providerId,\n model: source?.kind === 'model' && typeof source.model === 'string' ? source.model : providerId,\n usage: zeroUsage(),\n stopReason: content.some((block) => block.type === 'toolCall') ? 'toolUse' : 'stop',\n timestamp: 0,\n }\n}\n\nfunction flattenText(message: HarnessMessage): string {\n return message.content\n .filter((block) => block.type === 'text')\n .map((block) => (block as { text: string }).text)\n .join('')\n}\n\nfunction toolResultText(blocks: HarnessBlock[]): string {\n return blocks\n .map((block) => (block.type === 'text' ? (block as { text: string }).text : block.type === 'tool-result' ? toolResultText((block as { content: HarnessBlock[] }).content) : ''))\n .join('')\n}\n\nfunction toolsOf(options: HarnessGenerateOptions): PiTool[] | undefined {\n const tools = options.tools?.map((tool) => ({ name: tool.name, description: tool.description, parameters: tool.parameters }))\n return tools && tools.length > 0 ? tools : undefined\n}\n\nfunction assistantApiFor(options: HarnessGenerateOptions, entryEndpoints: readonly string[]): 'openai-completions' | 'openai-responses' {\n void options\n return modelApiForEntry(entryEndpoints)\n}\n\n/**\n * Text-only conversion (no image bytes needed). Images without resolved\n * bytes cannot be sent \u2014 callers must route image-bearing requests through\n * `toPiContextWithImages` instead.\n */\nexport function toPiContext(options: HarnessGenerateOptions, entryEndpoints: readonly string[] = []): PiContext {\n const providerId = options.provider\n const assistantApi = assistantApiFor(options, entryEndpoints)\n const toolNames = new Map<string, string>()\n const messages: PiMessage[] = []\n for (const message of options.messages) {\n if (message.role === 'system') {\n const text = flattenText(message)\n if (text.length > 0) messages.push({ role: 'user', content: text, timestamp: 0 })\n continue\n }\n if (message.role === 'assistant') {\n const assistant = toPiAssistant(message, providerId, assistantApi)\n for (const block of assistant.content) {\n if (block.type === 'toolCall') toolNames.set(block.id, block.name)\n }\n messages.push(assistant)\n continue\n }\n if (message.role === 'developer') continue\n if (message.role === 'tool') {\n const text = flattenText(message)\n messages.push({\n role: 'toolResult',\n toolCallId: message.toolCallId ?? '',\n toolName: message.toolCallId ? (toolNames.get(message.toolCallId) ?? 'unknown') : 'unknown',\n content: [{ type: 'text', text: text || '(no output)' }],\n isError: message.isError ?? false,\n timestamp: 0,\n })\n continue\n }\n for (const block of message.content) {\n if (block.type === 'image') {\n throw new Error('dsh-magpie-connect: image input requires the attachment service (use toPiContextWithImages)')\n }\n if (block.type === 'file') {\n // Files are projected to handle text by the harness; keep a stable\n // placeholder when one arrives unprojected.\n continue\n }\n }\n const text = flattenText(message)\n const fileText = message.content\n .filter((block) => block.type === 'file')\n .map((block) => `[file: ${(block as Extract<HarnessBlock, { type: 'file' }>).attachment.name}]`)\n .join('\\n')\n const combined = [text, fileText].filter((part) => part.length > 0).join('\\n')\n const results = message.content.filter((block) => block.type === 'tool-result') as Array<\n Extract<HarnessBlock, { type: 'tool-result' }>\n >\n if (combined.length > 0 || results.length === 0) {\n messages.push({ role: 'user', content: combined, timestamp: 0 })\n }\n for (const result of results) {\n messages.push({\n role: 'toolResult',\n toolCallId: result.toolCallId,\n toolName: toolNames.get(result.toolCallId) ?? 'unknown',\n content: [{ type: 'text', text: toolResultText(result.content) || '(no output)' }],\n isError: result.isError ?? false,\n timestamp: 0,\n })\n }\n }\n const context: PiContext = { messages }\n if (typeof options.system === 'string' && options.system.length > 0) context.systemPrompt = options.system\n const tools = toolsOf(options)\n if (tools) context.tools = tools\n return context\n}\n\n/**\n * Image-aware conversion: retained images resolve to request bytes via the\n * attachment service; offloaded images become placeholder text.\n */\nexport async function toPiContextWithImages(\n options: HarnessGenerateOptions,\n attachments: AttachmentStore,\n entryEndpoints: readonly string[] = [],\n): Promise<PiContext> {\n const providerId = options.provider\n const assistantApi = assistantApiFor(options, entryEndpoints)\n const requestImages = await prepareRequestImages(options.messages, attachments, options.signal)\n const toolNames = new Map<string, string>()\n const messages: PiMessage[] = []\n for (const message of options.messages) {\n if (message.role === 'system') {\n const text = flattenText(message)\n if (text.length > 0) messages.push({ role: 'user', content: text, timestamp: 0 })\n continue\n }\n if (message.role === 'assistant') {\n const assistant = toPiAssistant(message, providerId, assistantApi)\n for (const block of assistant.content) {\n if (block.type === 'toolCall') toolNames.set(block.id, block.name)\n }\n messages.push(assistant)\n continue\n }\n if (message.role === 'developer') continue\n if (message.role === 'tool') {\n messages.push({\n role: 'toolResult',\n toolCallId: message.toolCallId ?? '',\n toolName: message.toolCallId ? (toolNames.get(message.toolCallId) ?? 'unknown') : 'unknown',\n content: userContentToBlocks(message.content, requestImages),\n isError: message.isError ?? false,\n timestamp: 0,\n })\n continue\n }\n const content = userContentWithImages(message.content, requestImages)\n const results = message.content.filter((block) => block.type === 'tool-result') as Array<\n Extract<HarnessBlock, { type: 'tool-result' }>\n >\n // userContentWithImages already folds nested tool-results to text; emit\n // first-class toolResult messages for pi-ai tool continuity.\n if (typeof content === 'string' ? content.length > 0 || results.length === 0 : true) {\n messages.push({ role: 'user', content, timestamp: 0 })\n } else if (typeof content === 'string' && content.length === 0 && results.length === 0) {\n messages.push({ role: 'user', content, timestamp: 0 })\n }\n for (const result of results) {\n messages.push({\n role: 'toolResult',\n toolCallId: result.toolCallId,\n toolName: toolNames.get(result.toolCallId) ?? 'unknown',\n content: userContentToBlocks(result.content, requestImages),\n isError: result.isError ?? false,\n timestamp: 0,\n })\n }\n }\n const context: PiContext = { messages }\n if (typeof options.system === 'string' && options.system.length > 0) context.systemPrompt = options.system\n const tools = toolsOf(options)\n if (tools) context.tools = tools\n return context\n}\n\nfunction userContentToBlocks(blocks: HarnessBlock[], requestImages: Map<string, RequestImageVersion>): PiContentBlock[] {\n const converted = userContentWithImages(blocks, requestImages)\n if (typeof converted === 'string') return [{ type: 'text', text: converted || '(no output)' }]\n return converted.length > 0 ? converted : [{ type: 'text', text: '(no output)' }]\n}\n", "/**\n * Stall watchdog for upstream event streams.\n *\n * pi-ai surfaces transport failures as `error` events, but a silently\n * stalled SSE connection (headers sent, then no bytes ever again) produces\n * *no* event at all: the consumer's `for await` blocks until the SDK-level\n * overall timeout or the harness timeout. This wrapper races every `next()`\n * against a timer and, on expiry, aborts the upstream (best effort, via\n * `onTimeout`) and yields a synthetic `error` event so the normal chunk\n * translation turns the stall into a fast `TIMEOUT` finish instead of a\n * minutes-long hang.\n *\n * Two phases: time-to-first-event (queueing happens here) and idle-between-\n * events (a healthy stream emits frequently). Either phase accepts a\n * non-positive value to disable its timer (wait forever, the old behavior).\n */\n\nimport type { PiEvent } from './events.ts'\n\nexport interface StallTimeouts {\n /** Max wait for the first upstream event (connection + queueing). */\n firstEventTimeoutMs: number\n /** Max silence between two upstream events once streaming. */\n idleTimeoutMs: number\n}\n\nconst TIMED_OUT: unique symbol = Symbol('watchdog-timed-out')\n\nfunction raceTimeout<T>(promise: Promise<T>, ms: number): Promise<T | typeof TIMED_OUT> {\n let timer!: ReturnType<typeof setTimeout>\n const timeout = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), ms)\n const t = timer as unknown as { unref?: () => void }\n t.unref?.()\n })\n const cleanup = (): void => clearTimeout(timer)\n return Promise.race([promise.then((value) => {\n cleanup()\n return value\n }, (err: unknown) => {\n cleanup()\n throw err\n }), timeout])\n}\n\nfunction stallErrorEvent(model: string, waitMs: number, first: boolean): PiEvent {\n const phase = first ? 'waiting for the first upstream event' : 'waiting for the next upstream event'\n return {\n type: 'error',\n error: {\n api: 'openai-completions',\n provider: 'dsh-magpie-connect',\n model,\n content: [],\n usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },\n stopReason: 'error',\n // Must read as a timeout (events.ts classifies on /timeout/i -> TIMEOUT).\n errorMessage: `upstream stream timeout: no upstream events for ${Math.round(waitMs / 1000)}s while ${phase} (model \"${model}\") \u2014 the stalled stream was aborted`,\n timestamp: Date.now(),\n },\n } as PiEvent\n}\n\nexport async function* withStallTimeout(\n events: AsyncIterable<PiEvent>,\n timeouts: StallTimeouts,\n options: { model?: string; onTimeout?: () => void } = {},\n): AsyncGenerator<PiEvent> {\n const iterator = events[Symbol.asyncIterator]()\n try {\n let first = true\n for (;;) {\n const waitMs = first ? timeouts.firstEventTimeoutMs : timeouts.idleTimeoutMs\n if (!(waitMs > 0 && Number.isFinite(waitMs))) {\n const next = await iterator.next()\n if (next.done) return\n first = false\n yield next.value\n continue\n }\n const next = await raceTimeout(iterator.next(), waitMs)\n if (next === TIMED_OUT) {\n try {\n options.onTimeout?.()\n } catch {\n // Teardown must never mask the stall itself.\n }\n yield stallErrorEvent(options.model ?? 'unknown', waitMs, first)\n return\n }\n if (next.done) return\n first = false\n yield next.value\n }\n } finally {\n // Best-effort detach without awaiting: the upstream may be the very thing\n // that is stuck, so waiting for its return() could hang teardown too.\n void Promise.resolve(iterator.return?.()).catch(() => {})\n }\n}\n", "/**\n * Plugin configuration (cordis config object, injected via cordis.patch.yml).\n *\n * The adapter registers a DSH LlmAdapter streaming directly from the Magpie\n * LAN gateway (the configured API root): chat completions for most models plus the\n * Responses API for responses-only models. No child process, no proxy/pool.\n * The LAN gateway needs no credential \u2014 `apiKey` defaults to a sentinel that\n * satisfies the pi-ai client without changing the wire.\n *\n * The settings sidebar page can override `baseUrl`/`apiKey` at runtime (saved\n * to `<dataDir>/settings.json`, applied without restart) and hides models\n * from the picker. Page values win per-field; patch config is the fallback.\n */\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nexport interface DshMagpieConnectConfig {\n /**\n * Provider route id registered into DSH (the grouping key in the model\n * picker). Changing it after sessions already reference the old id will\n * orphan those model selections \u2014 prefer `displayName` for a cosmetic\n * rename.\n */\n providerId?: string\n /**\n * Display name shown in the model picker for this provider.\n * Defaults to the explicit `providerId` when one is given,\n * otherwise to `'Magpie'`.\n */\n displayName?: string\n /**\n * Versioned API root, e.g. `http://127.0.0.1:3425/v1`. The version is part of\n * the value, not something this plugin adds: a gateway that later serves\n * `/v2` or `/v3` is reached by changing this one string. Trailing slashes are\n * stripped; the path is kept.\n */\n baseUrl?: string\n /**\n * Gateway credential. Required: an empty key leaves the plugin unconfigured\n * even though `baseUrl` has a default, so the picker stays empty until it is\n * set (both are required by `isConfigured`).\n */\n apiKey?: string\n /**\n * Plugin state directory (status snapshot, settings file, catalog cache).\n * Defaults to `~/.dsh-magpie-connect`.\n */\n dataDir?: string\n /** Model list refresh interval in seconds. */\n refreshSeconds?: number\n /**\n * Connection-setup retries for transient upstream failures (429/5xx with\n * backoff). Turn-level retries stay owned by DSH.\n */\n maxRetries?: number\n /** Overall upstream request cap in milliseconds. */\n timeoutMs?: number\n /**\n * Stall watchdog: max wait for the first upstream event in milliseconds\n * (queueing happens here). Non-positive disables that phase.\n */\n firstEventTimeoutMs?: number\n /**\n * Stall watchdog: max silence between upstream events in milliseconds.\n * Non-positive disables that phase.\n */\n idleTimeoutMs?: number\n}\n\nexport const defaults = {\n providerId: 'dsh-magpie-connect',\n displayName: 'Magpie',\n baseUrl: 'http://127.0.0.1:3425/v1',\n apiKey: '',\n refreshSeconds: 300,\n maxRetries: 2,\n timeoutMs: 300_000,\n firstEventTimeoutMs: 90_000,\n idleTimeoutMs: 60_000,\n} as const\n\nexport type ResolvedConfig = Required<\n Pick<DshMagpieConnectConfig, 'providerId' | 'refreshSeconds'>\n> &\n DshMagpieConnectConfig & { displayName: string; baseUrl: string; apiKey: string; dataDir: string }\n\nexport function resolveConfig(config: DshMagpieConnectConfig = {}): ResolvedConfig {\n const providerId = config.providerId ?? defaults.providerId\n return {\n ...defaults,\n ...config,\n providerId,\n displayName: config.displayName ?? (config.providerId ?? defaults.displayName),\n baseUrl: config.baseUrl ?? defaults.baseUrl,\n apiKey: config.apiKey ?? defaults.apiKey,\n dataDir: config.dataDir ?? join(homedir(), '.dsh-magpie-connect'),\n }\n}\n", "import { readFile, writeFile, mkdir, rename, rm } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Page-owned runtime settings, persisted to a local JSON file.\n *\n * Precedence for the effective endpoint: settings page (this file, only the\n * fields the user actually saved) > cordis patch config > built-in defaults.\n * Hidden models only ever come from this file \u2014 the patch config has no such\n * field, so an empty/absent list means \"show everything\".\n */\n\nexport interface MagpiePageSettings {\n baseUrl?: string\n apiKey?: string\n hiddenModels?: string[]\n}\n\nexport interface EffectiveEndpoint {\n baseUrl: string\n apiKey: string\n}\n\nexport const SETTINGS_FILE = 'settings.json'\n\nexport function defaultSettingsPath(dataDir: string): string {\n return join(dataDir, SETTINGS_FILE)\n}\n\n/**\n * Normalize an API base URL: must be http(s), no trailing slash, path kept.\n *\n * The path is load-bearing, not a mistake to be trimmed: the gateway versions\n * its API (`/v1`, and later `/v2`, `/v3`), so this value *is* the prefix every\n * request is built on. The plugin appends only the resource (`/models`,\n * `/chat/completions`), never a version of its own \u2014 an origin-only value\n * would make a future `/v2` unreachable.\n */\nexport function normalizeBaseUrl(raw: unknown): string {\n if (typeof raw !== 'string' || raw.trim() === '') throw new Error('dsh-magpie-connect: baseUrl must be a non-empty http(s) URL')\n const trimmed = raw.trim()\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n throw new Error(`dsh-magpie-connect: baseUrl is not a valid URL: ${trimmed}`)\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`dsh-magpie-connect: baseUrl must use http(s): ${trimmed}`)\n }\n // Query and fragment are never part of a base URL and would corrupt every\n // appended path; drop them rather than storing a value the wire ignores.\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`\n}\n\n/** Normalize a hidden-model list: strings only, deduped, order kept. */\nexport function normalizeHiddenModels(raw: unknown): string[] {\n if (raw === undefined) return []\n if (!Array.isArray(raw)) throw new Error('dsh-magpie-connect: hiddenModels must be an array of model ids')\n const out: string[] = []\n for (const entry of raw) {\n if (typeof entry !== 'string') throw new Error('dsh-magpie-connect: hiddenModels must be an array of model ids')\n const id = entry.trim()\n if (id !== '' && !out.includes(id)) out.push(id)\n }\n return out\n}\n\n/** Validate + normalize a page payload (full or partial). Unknown keys are dropped. */\nexport function normalizePageSettings(raw: unknown): MagpiePageSettings {\n if (!raw || typeof raw !== 'object') throw new Error('dsh-magpie-connect: settings payload must be an object')\n const record = raw as Record<string, unknown>\n const out: MagpiePageSettings = {}\n if (record.baseUrl !== undefined) out.baseUrl = normalizeBaseUrl(record.baseUrl)\n if (record.apiKey !== undefined) {\n if (typeof record.apiKey !== 'string' || record.apiKey.trim() === '') {\n throw new Error('dsh-magpie-connect: apiKey is required and must not be empty')\n }\n // Surrounding whitespace is always a paste artifact in Bearer keys.\n out.apiKey = record.apiKey.trim()\n }\n if (record.hiddenModels !== undefined) out.hiddenModels = normalizeHiddenModels(record.hiddenModels)\n return out\n}\n\n/**\n * Merge patch config with page settings: page wins per-field, but only for\n * fields it actually saved (absent page fields fall back to patch/defaults).\n */\nexport function resolveEffectiveEndpoint(\n patch: { baseUrl: string; apiKey: string },\n page: MagpiePageSettings,\n): EffectiveEndpoint {\n return {\n baseUrl: page.baseUrl ?? patch.baseUrl,\n apiKey: page.apiKey ?? patch.apiKey,\n }\n}\n\n/** Fully configured only when both URL and key are present. Either missing gates everything. */\nexport function isConfigured(endpoint: EffectiveEndpoint): boolean {\n return endpoint.baseUrl !== '' && endpoint.apiKey !== ''\n}\n\n/** In-memory settings with file persistence. Synchronous reads for the hot path. */\nexport class SettingsStore {\n #path?: string\n #settings: MagpiePageSettings = {}\n #listeners = new Set<() => void>()\n\n constructor(options: { path?: string } = {}) {\n this.#path = options.path\n }\n\n /** Load persisted settings (missing/corrupt file reads as empty, never throws). */\n async load(): Promise<MagpiePageSettings> {\n if (!this.#path) return this.get()\n try {\n const raw = JSON.parse(await readFile(this.#path, 'utf8')) as unknown\n this.#settings = normalizePageSettings(raw)\n } catch {\n this.#settings = {}\n }\n return this.get()\n }\n\n get(): MagpiePageSettings {\n return { ...this.#settings, hiddenModels: [...(this.#settings.hiddenModels ?? [])] }\n }\n\n /** Merge a partial payload, persist, and notify listeners. */\n async save(partial: unknown): Promise<MagpiePageSettings> {\n const normalized = normalizePageSettings(partial)\n this.#settings = { ...this.#settings, ...normalized }\n await this.persist()\n this.#emit()\n return this.get()\n }\n\n /** Replace the whole document (used at startup after load). */\n replace(next: MagpiePageSettings): void {\n this.#settings = { ...next, hiddenModels: [...(next.hiddenModels ?? [])] }\n this.#emit()\n }\n\n onChange(listener: () => void): () => void {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n async persist(): Promise<void> {\n if (!this.#path) return\n const tmp = `${this.#path}.${process.pid}.tmp`\n await mkdir(dirname(this.#path), { recursive: true })\n await writeFile(tmp, JSON.stringify(this.#settings, null, 2), 'utf8')\n await rm(this.#path, { force: true })\n await rename(tmp, this.#path)\n }\n\n #emit(): void {\n for (const listener of [...this.#listeners]) {\n try {\n listener()\n } catch {\n // listeners must never break the save path\n }\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { join } from 'node:path'\nimport { writeFile } from 'node:fs/promises'\n\nimport {\n ModelCatalog,\n defaultCachePath,\n fetchMagpieModels,\n type CatalogSnapshot,\n type MagpieModelEntry,\n} from './adapter/catalog.ts'\nimport { MagpieAdapter } from './adapter/magpie-adapter.ts'\nimport type { AttachmentStore } from './adapter/messages.ts'\nimport { resolveConfig, type DshMagpieConnectConfig } from './config.ts'\nimport {\n SettingsStore,\n defaultSettingsPath,\n isConfigured,\n normalizeBaseUrl,\n normalizePageSettings,\n resolveEffectiveEndpoint,\n} from './settings.ts'\n\n/**\n * dsh-magpie-connect DSH cordis plugin entry.\n *\n * Registers a DSH LlmAdapter streaming directly from the Magpie LAN gateway\n * (marketplace shape: no child process, no binary, no proxy). The model\n * catalog warms up in the background (live gateway list with disk cache +\n * static snapshot fallback).\n *\n * The Settings sidebar page (client half) talks to the REST routes below:\n * connection endpoint (baseUrl/apiKey) applies live without restart, and the\n * hidden-model list filters the picker immediately.\n *\n * dispose(): stop the catalog refresh loop. The cordis fiber disposal\n * guarantees this runs on plugin reload/unload and on DSH shutdown.\n */\n\n// Minimal structural typing against the host ctx; keeps the plugin independent\n// of the exact @deepseek-ai/cordis version DSH ships.\nexport interface PluginContext {\n logger: { info(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void }\n llm?: { registerAdapter(providers: string[], adapter: unknown): unknown }\n get?(key: string): unknown\n inject?(deps: string[], fn: (ctx: Record<string, unknown>) => unknown): unknown\n effect?(fn: () => () => void): unknown\n on?(event: string, listener: (...args: never[]) => unknown): () => void\n /**\n * Cordis `ctx.emit` (mixed in from the events service). Used to publish\n * `llm/adapters-updated` when this plugin's own catalog moves, without\n * touching the adapter registry.\n */\n emit?(event: string, ...args: unknown[]): unknown\n}\n\nexport const name = 'dsh-magpie-connect'\nexport const inject = ['llm'] as const\n\n/** Browser-facing REST root for the settings page (served under the shared /api channel). */\nexport const SETTINGS_API = '/api/magpie-settings'\nexport const MODELS_API = '/api/magpie-models'\nexport const TEST_API = '/api/magpie-test'\n/**\n * Candidate discovery: asks the endpoint the form currently shows (including a\n * key typed but not yet saved) what it serves. The reply is candidates the\n * user picks from \u2014 never configuration written behind them.\n */\nexport const DISCOVER_API = '/api/magpie-discover'\n\n/**\n * How long a settings save waits for the catalog re-read it triggered.\n *\n * The page re-reads rows right after the response, so the save must not answer\n * before a fast gateway has been read; but the credential write is already\n * durable, so a slow gateway must not hold the request open either.\n */\nconst SAVE_REFRESH_WAIT_MS = 5000\n\n/** Backend generation behind the shared settings routes (newest wins). */\nexport interface SettingsBackend {\n store: SettingsStore\n catalog: ModelCatalog\n patchEndpoint: { baseUrl: string; apiKey: string }\n effective: () => { baseUrl: string; apiKey: string }\n applyPageSettings: () => void\n}\n\ntype FetchRegister = (route: {\n path: string\n methods: string[]\n requestBody: string\n fetch: (request: Request) => Promise<Response>\n}) => () => void | Promise<void>\n\ninterface ModelRow {\n id: string\n displayName: string\n contextWindow?: number\n maxTokens?: number\n image: boolean\n responsesOnly: boolean\n reasoning: boolean\n efforts: string[]\n hidden: boolean\n}\n\n/** One discovered candidate: a {@link ModelRow} without this plugin's own visibility flag. */\nexport type CandidateRow = Omit<ModelRow, 'hidden'>\n\n/** True when the endpoints advertise responses but not chat completions. */\nfunction responsesOnlyFor(nativeEndpoints: readonly string[]): boolean {\n if (nativeEndpoints.length === 0) return false\n const hasCompletions = nativeEndpoints.some((endpoint) => endpoint.includes('/chat/completions'))\n return !hasCompletions && nativeEndpoints.some((endpoint) => endpoint.includes('/responses'))\n}\n\n/** Project one catalog entry (or a bare id) into the wire row the page renders. */\nfunction toRow(id: string, entry: MagpieModelEntry | undefined): CandidateRow {\n return {\n id,\n displayName: entry?.displayName ?? id,\n ...(entry?.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),\n ...(entry?.maxTokens !== undefined ? { maxTokens: entry.maxTokens } : {}),\n image: entry?.image ?? false,\n responsesOnly: responsesOnlyFor(entry?.nativeEndpoints ?? []),\n reasoning: entry?.reasoning ?? false,\n efforts: entry?.efforts ?? [],\n }\n}\n\nlet currentBackend: SettingsBackend | null = null\nlet liveRouteSets = 0\nlet routeDisposers: Array<() => void | Promise<void>> | null = null\n\n/** @internal Test hook: forget installed routes so the next apply reinstalls. */\nexport function __resetSettingsRoutes(): void {\n currentBackend = null\n liveRouteSets = 0\n routeDisposers = null\n}\n\nfunction jsonResponse(status: number, payload: unknown): Response {\n return new Response(JSON.stringify(payload), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },\n })\n}\n\nconst routeOk = (value: unknown): Response => jsonResponse(200, { ok: true, value })\nconst routeFail = (error: unknown): Response =>\n jsonResponse(500, { ok: false, error: error instanceof Error ? error.message : String(error) })\n\nasync function readRouteBody(request: Request): Promise<unknown> {\n try {\n return await request.json()\n } catch {\n return {}\n }\n}\n\nfunction buildModelRows(backend: SettingsBackend): ModelRow[] {\n const { store, catalog } = backend\n const hidden = new Set(store.get().hiddenModels ?? [])\n // Hidden ids survive in the list even when the gateway no longer serves\n // them: a stale entry must stay reachable so it can be un-hidden again.\n const ids = new Set<string>([...catalog.list(), ...hidden])\n return [...ids].sort().map((id) => ({ ...toRow(id, catalog.getEntry(id)), hidden: hidden.has(id) }))\n}\n\nfunction backendOrThrow(): SettingsBackend {\n if (!currentBackend) throw new Error('dsh-magpie-connect: settings backend not ready')\n return currentBackend\n}\n\n/**\n * One registered route: the connection registry keys routes by PATH and\n * rejects a second registration of the same path, so every method of one\n * endpoint must share a single entry and dispatch inside its handler. Two\n * entries on `SETTINGS_API` (GET + POST) silently lose the second one \u2014 the\n * save button then answers `404 not found` forever.\n */\ninterface RouteDefinition {\n path: string\n methods: string[]\n requestBody: string\n fetch: (request: Request) => Promise<Response>\n}\n\nasync function handleSettingsGet(): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const page = backend.store.get()\n const endpoint = resolveEffectiveEndpoint(backend.patchEndpoint, page)\n return routeOk({\n baseUrl: endpoint.baseUrl,\n apiKey: endpoint.apiKey,\n hiddenModels: page.hiddenModels ?? [],\n fromPage: page,\n fromPatch: backend.patchEndpoint,\n })\n } catch (error) {\n return routeFail(error)\n }\n}\n\nasync function handleSettingsPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = await readRouteBody(request)\n const normalized = normalizePageSettings(body)\n // Prune ids the gateway no longer serves: the page only ever shows live\n // rows, so a stale hidden id could otherwise never be seen or cleared.\n if (normalized.hiddenModels !== undefined) {\n normalized.hiddenModels = backend.catalog.pruneHidden(normalized.hiddenModels)\n }\n const saved = await backend.store.save(normalized)\n backend.applyPageSettings()\n // A save can move the endpoint (or just the key), so re-read the directory\n // before answering: the page re-reads rows right after this response, and a\n // key-guarded gateway would otherwise still be serving the old, empty\n // snapshot when that read lands.\n //\n // Bounded: the write is already durable, so a gateway that answers slowly\n // (or not at all) must not turn a successful save into a client-side\n // timeout. Losing the race only costs a stale row, never the setting.\n if (normalized.baseUrl !== undefined || normalized.apiKey !== undefined) {\n await Promise.race([\n backend.catalog.refreshOnce(),\n new Promise((resolve) => setTimeout(resolve, SAVE_REFRESH_WAIT_MS)),\n ])\n }\n const endpoint = resolveEffectiveEndpoint(backend.patchEndpoint, saved)\n return routeOk({ baseUrl: endpoint.baseUrl, apiKey: endpoint.apiKey, hiddenModels: saved.hiddenModels ?? [] })\n } catch (error) {\n return routeFail(error)\n }\n}\n\nasync function handleModelsGet(): Promise<Response> {\n try {\n const backend = backendOrThrow()\n return routeOk({ models: buildModelRows(backend), snapshot: backend.catalog.snapshot() })\n } catch (error) {\n return routeFail(error)\n }\n}\n\n/** Resolve the endpoint one probe should ask: form values first, effective config as fallback. */\nfunction probeTarget(\n backend: SettingsBackend,\n body: { baseUrl?: unknown; apiKey?: unknown },\n): { origin: string; key: string } {\n const effective = backend.effective()\n const origin = normalizeBaseUrl(body.baseUrl ?? effective.baseUrl)\n const rawKey = typeof body.apiKey === 'string' ? body.apiKey : effective.apiKey\n const key = rawKey.trim()\n if (key === '') throw new Error('dsh-magpie-connect: apiKey is required \u2014 fill it in before asking the gateway')\n return { origin, key }\n}\n\n/**\n * Turn a gateway failure into copy the page can show. A 404 from the gateway\n * almost always means the origin path is wrong or the key is unknown or\n * unauthorized (many gateways hide existence behind 404 instead of 401).\n */\nfunction hintGatewayFailure(error: unknown): Response {\n const message = error instanceof Error ? error.message : String(error)\n const hinted =\n /HTTP 404/.test(message) && !/\u7F51\u5173\u8FD4\u56DE 404/.test(message)\n ? `${message}\uFF08\u7F51\u5173\u8FD4\u56DE 404\uFF1A\u591A\u4E3A API \u5730\u5740\u8DEF\u5F84\u4E0D\u5BF9\uFF0C\u6216\u8BE5 Key \u65E0\u6548/\u65E0\u6743\u8BBF\u95EE\uFF09`\n : message\n return routeFail(hinted)\n}\n\nasync function handleTestPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = (await readRouteBody(request)) as { baseUrl?: unknown; apiKey?: unknown }\n const { origin, key } = probeTarget(backend, body)\n const models = await fetchMagpieModels(origin, fetch, { headers: { authorization: `Bearer ${key}` } })\n return routeOk({ count: models.size, models: [...models.keys()].sort().slice(0, 50) })\n } catch (error) {\n return hintGatewayFailure(error)\n }\n}\n\n/**\n * Candidate discovery for the fetch-available-models dialog.\n *\n * Unlike {@link handleTestPost} (a count for the connection check) this returns\n * full rows so the picker can show the same chips the list does, and it caps\n * nothing: the dialog filters client-side. The origin and key come from the\n * form, so a provider can be filled in one pass instead of save-then-return.\n */\nasync function handleDiscoverPost(request: Request): Promise<Response> {\n try {\n const backend = backendOrThrow()\n const body = (await readRouteBody(request)) as { baseUrl?: unknown; apiKey?: unknown }\n const { origin, key } = probeTarget(backend, body)\n const models = await fetchMagpieModels(origin, fetch, { headers: { authorization: `Bearer ${key}` } })\n const candidates = [...models.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([id, entry]) => toRow(id, entry))\n return routeOk({ count: candidates.length, models: candidates })\n } catch (error) {\n return hintGatewayFailure(error)\n }\n}\n\nfunction routeDefinitions(): RouteDefinition[] {\n return [\n {\n path: SETTINGS_API,\n methods: ['GET', 'POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => (request.method === 'POST' ? handleSettingsPost(request) : handleSettingsGet()),\n },\n {\n path: MODELS_API,\n methods: ['GET'],\n requestBody: 'buffered',\n fetch: () => handleModelsGet(),\n },\n {\n path: TEST_API,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => handleTestPost(request),\n },\n {\n path: DISCOVER_API,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: (request: Request) => handleDiscoverPost(request),\n },\n ]\n}\n\n/**\n * Install the settings-page routes on the authenticated /api channel.\n *\n * Process-wide singleton semantics: hot reload can run overlapping\n * generations of this plugin in one process, and the connection registry\n * rejects duplicate paths. The routes install once and are shared; the last\n * generation out uninstalls. Handlers resolve through `currentBackend`, so\n * they always serve the newest generation.\n */\nfunction installSettingsRoutes(\n ctx: PluginContext,\n logger: PluginContext['logger'],\n): void {\n try {\n if (typeof ctx.inject !== 'function') return\n ctx.inject(['connection'], (cctx: Record<string, unknown>) => {\n const connection = cctx.connection as { fetch?: { register?: FetchRegister } } | undefined\n const register = connection?.fetch?.register\n if (typeof register !== 'function') {\n logger.warn('dsh-magpie-connect: connection fetch registry unavailable; settings page API disabled')\n return\n }\n liveRouteSets += 1\n try {\n if (routeDisposers === null) {\n const installed: Array<() => void | Promise<void>> = []\n try {\n for (const definition of routeDefinitions()) {\n try {\n installed.push(register(definition))\n } catch (error) {\n // A live sibling generation already owns this path; share it.\n // Only ever expected for a whole path we also declare \u2014 since\n // every method of one path shares one entry, losing one here\n // means the sibling serves it identically.\n if (error instanceof Error && /already registered/.test(error.message)) {\n logger.warn(`dsh-magpie-connect: ${definition.path} already registered by another generation; sharing it`)\n continue\n }\n throw error\n }\n }\n } catch (error) {\n for (const dispose of installed.reverse()) {\n try {\n const result = dispose()\n if (result instanceof Promise) result.catch(() => {})\n } catch {\n // roll back best-effort\n }\n }\n throw error\n }\n routeDisposers = installed\n }\n } catch (error) {\n liveRouteSets -= 1\n logger.warn(`dsh-magpie-connect: settings API unavailable: ${error instanceof Error ? error.message : String(error)}`)\n throw error\n }\n return async () => {\n liveRouteSets -= 1\n if (liveRouteSets <= 0) {\n liveRouteSets = 0\n const owned = routeDisposers\n routeDisposers = null\n if (owned) {\n for (const dispose of owned.reverse()) await dispose()\n }\n }\n }\n })\n } catch (error) {\n logger.warn(`dsh-magpie-connect: settings API unavailable: ${error instanceof Error ? error.message : String(error)}`)\n }\n}\n\nexport function apply(\n ctx: PluginContext,\n config: DshMagpieConnectConfig = {},\n): { ready: Promise<{ version: string }> } {\n const logger = ctx.logger\n const cfg = resolveConfig(config)\n const ready = Promise.resolve({ version: 'adapter' })\n\n if (!ctx.llm || typeof ctx.llm.registerAdapter !== 'function') {\n logger.error('dsh-magpie-connect: llm service unavailable; adapter cannot register')\n return { ready }\n }\n\n const dataDir = cfg.dataDir\n const statusPath = join(dataDir, 'adapter-status.json')\n const writeStatus = (status: CatalogSnapshot, lastError: string): void => {\n void writeFile(\n statusPath,\n JSON.stringify({ ...status, lastError, writtenAt: new Date().toISOString() }, null, 2),\n 'utf8',\n ).catch(() => {})\n }\n\n const store = new SettingsStore({ path: defaultSettingsPath(dataDir) })\n const patchEndpoint = { baseUrl: cfg.baseUrl, apiKey: cfg.apiKey }\n const effective = () => resolveEffectiveEndpoint(patchEndpoint, store.get())\n\n /**\n * Publish `llm/adapters-updated`, the one notification DSH's model\n * directory listens to.\n *\n * The browser caches one `modelCatalog()` read per Host generation and\n * re-reads only on this event. Registering an adapter fires it once, but\n * nothing does for a pure in-memory change, so hiding models, switching\n * the gateway origin, or a refresh that moved the list all left the picker\n * on the stale snapshot until restart. `dsh-workbuddy-connect` solves the\n * same problem identically for its visibility toggles.\n */\n const announceTopology = (): void => {\n if (typeof ctx.emit !== 'function') return\n try {\n ctx.emit('llm/adapters-updated')\n } catch (error) {\n logger.warn(`dsh-magpie-connect: llm/adapters-updated emit failed: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n const catalog = new ModelCatalog({\n baseUrl: effective().baseUrl,\n apiKey: effective().apiKey,\n refreshSeconds: cfg.refreshSeconds,\n cachePath: defaultCachePath(dataDir),\n onRefresh: (status, lastError) => {\n writeStatus(status, lastError)\n if (lastError) logger.warn(`dsh-magpie-connect: catalog refresh issue: ${lastError}`)\n },\n onInvalidate: announceTopology,\n })\n /**\n * Apply page settings to the live catalog + adapter (no restart needed).\n *\n * The endpoint is a pair: the gateway guards `GET /models` with the same key\n * as inference, so the key is part of the catalog's identity, not just the\n * URL. A key-only edit therefore re-points the catalog too \u2014 otherwise the\n * refresh keeps 401ing and the picker keeps serving the static snapshot,\n * which is why a model just ticked in the fetch dialog never appeared.\n */\n const applyPageSettings = (): void => {\n const page = store.get()\n const endpoint = resolveEffectiveEndpoint(patchEndpoint, page)\n // The gate: URL and key are both required. Anything missing clears the\n // catalog API root, which empties the picker and calls fail fast.\n // The value is the versioned root (`\u2026/v1`), passed through untouched.\n const configured = isConfigured(endpoint)\n catalog.setEndpoint(configured ? endpoint.baseUrl.replace(/\\/+$/, '') : '', configured ? endpoint.apiKey : '')\n catalog.setHidden(page.hiddenModels ?? [])\n }\n const adapter = new MagpieAdapter(catalog, {\n providerId: cfg.providerId,\n displayName: cfg.displayName,\n magpieBaseUrl: cfg.baseUrl,\n apiKey: cfg.apiKey,\n maxRetries: cfg.maxRetries,\n timeoutMs: cfg.timeoutMs,\n firstEventTimeoutMs: cfg.firstEventTimeoutMs,\n idleTimeoutMs: cfg.idleTimeoutMs,\n runtime: {\n baseUrl: () => {\n const endpoint = effective()\n return isConfigured(endpoint) ? endpoint.baseUrl.replace(/\\/+$/, '') : ''\n },\n apiKey: () => effective().apiKey,\n },\n resolveAttachments: () => {\n try {\n const attachments = typeof ctx.get === 'function' ? (ctx.get('attachments') as AttachmentStore | undefined) : undefined\n return attachments && typeof attachments.readImageRequest === 'function' ? attachments : undefined\n } catch {\n return undefined\n }\n },\n })\n store.onChange(applyPageSettings)\n\n // Register immediately: the provider must appear in the selector right\n // away, even while the catalog is still warming up (listModels is read\n // live at selector time, so models appear as refreshes land).\n ctx.llm.registerAdapter([cfg.providerId], adapter)\n logger.info(`dsh-magpie-connect: adapter registered for \"${cfg.providerId}\" (catalog warms up in background)`)\n void (async () => {\n await store.load()\n applyPageSettings()\n await catalog.start().catch((err) => {\n logger.error(`dsh-magpie-connect: catalog start failed: ${err instanceof Error ? err.message : String(err)}`)\n })\n })().catch((err) => {\n logger.error(`dsh-magpie-connect: startup failed: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n // Suppress the auto-generated config form: this plugin owns a dedicated\n // settings page (client half). Guarded \u2014 compositions without the settings\n // service simply skip it.\n try {\n if (typeof ctx.inject === 'function') {\n ctx.inject(['settings'], (settingsCtx: Record<string, unknown>) => {\n try {\n const scoped = settingsCtx as {\n effect?: (fn: () => unknown, label?: string) => unknown\n settings?: { configure?: (policy: unknown, fiber: unknown) => unknown }\n }\n const fiber = (ctx as unknown as Record<string, unknown>).fiber\n scoped.effect?.(() => scoped.settings?.configure?.({ auto: false }, fiber), 'dsh-magpie-connect: custom settings page')\n } catch {\n // presentation policy is best-effort\n }\n })\n }\n } catch {\n // presentation policy is best-effort\n }\n\n // Newest generation wins: shared route handlers below always resolve through\n // here, so the settings page talks to the latest store/catalog/adapter even\n // when an older generation installed the routes.\n currentBackend = { store, catalog, patchEndpoint, effective, applyPageSettings }\n\n installSettingsRoutes(ctx, logger)\n\n const maybeEffect = (ctx as { effect?: PluginContext['effect'] }).effect\n if (typeof maybeEffect === 'function') {\n maybeEffect.call(ctx, () => () => {\n catalog.stop()\n })\n }\n return { ready }\n}\n\nexport { MagpieAdapter } from './adapter/magpie-adapter.ts'\nexport { ModelCatalog } from './adapter/catalog.ts'\nexport { SettingsStore, isConfigured, resolveEffectiveEndpoint } from './settings.ts'\nexport {\n resolveConfig,\n type DshMagpieConnectConfig,\n} from './config.ts'\n", "import { readFile, rename, rm, writeFile, mkdir } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Model directory for the Magpie LAN gateway.\n *\n * Single source: `GET {baseUrl}/models` already curates the servable set\n * with full per-model metadata (modalities, native endpoints, reasoning\n * ladders, context limits). There is no paid/free filter \u2014 everything listed\n * is exposed. A disk cache plus a compile-time static snapshot covers gateway\n * outages (the plugin still registers, the picker still lists).\n *\n * The gateway guards that endpoint with the same Bearer key as inference, so\n * every fetch here carries it: a keyless refresh is answered 401 and the\n * picker silently keeps the static snapshot instead of the live directory.\n *\n * `baseUrl` is the versioned API root (default `http://127.0.0.1:3425/v1`),\n * never an origin: the gateway may move to `/v2`, and the plugin must not know\n * that version.\n */\n\nexport interface MagpieModelEntry {\n id: string\n displayName: string\n contextWindow?: number\n maxTokens?: number\n /** Whether the gateway accepts image input for this model. */\n image: boolean\n /** Raw gateway `native_endpoints` (may be empty when undisclosed). */\n nativeEndpoints: string[]\n /** Whether the gateway reports thinking support. */\n reasoning: boolean\n /** Effort allowlist from `supported_reasoning_levels` (canonical order). */\n efforts?: string[]\n}\n\n/**\n * Thinking levels the gateway uses. pi-ai natively understands the minimal\u2192max\n * ladder; `none` (thinking disabled) and `ultra` (beyond max, e.g. Terra) are\n * gateway extensions the adapter forwards verbatim on the Responses /\n * completions wire instead of clamping.\n */\nexport const KNOWN_EFFORTS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'] as const\nexport const EXTENDED_EFFORTS = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const\n\nexport type KnownEffort = (typeof KNOWN_EFFORTS)[number]\nexport type ExtendedEffort = (typeof EXTENDED_EFFORTS)[number]\n\n/** Picker display order: `none` (disable) first, then increasing strength. */\nconst EFFORT_ORDER: readonly string[] = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']\n\nconst EFFORT_LABELS: Record<string, { name: string; description: string }> = {\n none: { name: 'None', description: '\u5173\u95ED\u601D\u8003\uFF0C\u76F4\u63A5\u56DE\u7B54' },\n minimal: { name: 'Minimal', description: '\u6700\u5C11\u601D\u8003\uFF0C\u6700\u5FEB' },\n low: { name: 'Low', description: '\u8F7B\u91CF\u601D\u8003\uFF0C\u9002\u5408\u7B80\u5355\u95EE\u7B54' },\n medium: { name: 'Medium', description: '\u9ED8\u8BA4\u6863\u4F4D\uFF0C\u5747\u8861\u8D28\u91CF\u4E0E\u901F\u5EA6' },\n high: { name: 'High', description: '\u6DF1\u5EA6\u601D\u8003\uFF0C\u9002\u5408\u590D\u6742\u7F16\u7801' },\n xhigh: { name: 'Extra high', description: '\u8D85\u9AD8\u601D\u8003\u5F3A\u5EA6' },\n max: { name: 'Max', description: '\u6700\u5F3A\u601D\u8003\uFF08pi-ai \u6863\u4F4D\uFF09\uFF0C\u6700\u6162' },\n ultra: { name: 'Ultra', description: '\u6781\u9650\u601D\u8003\uFF08\u7F51\u5173\u76F4\u901A\uFF09\uFF0C\u6700\u6162\u6700\u8D35' },\n}\n\nexport interface ReasoningEffortChoice {\n id: string\n name: string\n description?: string\n}\n\n/** Normalize one gateway effort spelling; unknown spellings are dropped. */\nexport function normalizeEffort(raw: unknown): string | undefined {\n if (typeof raw !== 'string') return undefined\n const level = raw.trim().toLowerCase()\n return (EFFORT_ORDER as readonly string[]).includes(level) ? level : undefined\n}\n\n/** Canonicalize an effort list: keep known spellings, dedupe, order canonically. */\nexport function canonicalEfforts(values: readonly unknown[] | undefined): string[] | undefined {\n if (!Array.isArray(values)) return undefined\n const out: string[] = []\n for (const value of values) {\n const level = normalizeEffort(typeof value === 'object' && value !== null ? (value as { effort?: unknown }).effort ?? value : value)\n if (level !== undefined && !out.includes(level)) out.push(level)\n }\n if (out.length === 0) return undefined\n return EFFORT_ORDER.filter((level) => out.includes(level))\n}\n\n/** Build picker efforts for an allowlist (display order = none\u2192minimal\u2192\u2026\u2192ultra). */\nexport function effortsFor(efforts: readonly string[] | undefined): ReasoningEffortChoice[] {\n const allowed = new Set(efforts ?? [])\n return EFFORT_ORDER.filter((level) => allowed.has(level)).map((level) => {\n const label = EFFORT_LABELS[level] ?? { name: level, description: '' }\n return { id: level, name: label.name, description: label.description }\n })\n}\n\n/**\n * pi-ai `thinkingLevelMap` for an effort allowlist. `off: null` is\n * load-bearing: without it pi-ai falls back to `reasoning.effort: 'none'`\n * on the Responses lane, which the gateway rejects with 400. Every\n * non-listed known level maps to null so `getSupportedThinkingLevels` hides\n * it and `clampThinkingLevel` folds stale values to the nearest listed one.\n * Gateway extensions (`none`/`ultra`) never enter the map \u2014 the adapter\n * forwards them via `provider.stream` instead of `streamSimple`.\n */\nexport function thinkingLevelMapFor(efforts: readonly string[] | undefined): Record<string, string | null> {\n const allowed = new Set(efforts ?? [])\n const map: Record<string, string | null> = { off: null }\n for (const level of KNOWN_EFFORTS) map[level] = allowed.has(level) ? level : null\n return map\n}\n\n/**\n * True when the model must go through `/v1/responses` instead of chat\n * completions: the gateway lists responses as the only native endpoint\n * (Muse Spark, Codex/Grok lanes). Anything advertising\n * `/v1/chat/completions` \u2014 including multi-endpoint models \u2014 rides the\n * completions lane for better tool support.\n */\nexport function requiresResponsesApiEntry(nativeEndpoints: readonly string[]): boolean {\n const hasCompletions = nativeEndpoints.some((endpoint) => endpoint.includes('/chat/completions'))\n if (hasCompletions) return false\n return nativeEndpoints.some((endpoint) => endpoint.includes('/responses'))\n}\n\n/** pi-ai `model.api` value for the given native endpoints. */\nexport function modelApiForEntry(nativeEndpoints: readonly string[]): 'openai-completions' | 'openai-responses' {\n return requiresResponsesApiEntry(nativeEndpoints) ? 'openai-responses' : 'openai-completions'\n}\n\n/**\n * Lane-specific correction for gateway-advertised windows the execution\n * layer does not honor. The workbuddy-ai lane reports context_window\n * 1000000 but rejects anything over 100000 (observed 2026-10-08: 400\n * \"prompt is too long: 100001 tokens > 100000 maximum\"). Over-declaring\n * breaks both turns and /compact summaries (the summary request itself\n * overshoots); under-declaring only compacts early, so clamp down.\n */\nfunction clampLaneWindow(id: string, value: number | undefined): number | undefined {\n if (value === undefined) return undefined\n if (id.startsWith('workbuddy-ai/')) return Math.min(value, 100_000)\n return value\n}\n\n/** Decode one gateway model object into a catalog entry. */\nexport function decodeModel(raw: unknown): MagpieModelEntry | undefined {\n if (!raw || typeof raw !== 'object') return undefined\n const record = raw as Record<string, unknown>\n const id = record.id\n if (typeof id !== 'string' || id.length === 0) return undefined\n const num = (value: unknown): number | undefined =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined\n const displayName =\n (typeof record.display_name === 'string' && record.display_name) ||\n (typeof record.magpie_label === 'string' && record.magpie_label) ||\n id\n const contextWindow = clampLaneWindow(id, num(record.context_window) ?? num(record.context_length) ?? num(record.max_input_tokens))\n const maxTokens = num(record.max_output_tokens)\n const modalities = record.modalities as { input?: unknown } | undefined\n const inputModalities = Array.isArray(modalities?.input) ? modalities.input.map(String).map((s) => s.toLowerCase()) : []\n const image = inputModalities.includes('image')\n const nativeEndpoints = Array.isArray(record.native_endpoints)\n ? (record.native_endpoints as unknown[]).filter((e): e is string => typeof e === 'string')\n : []\n const reasoning = record.reasoning === true\n const levels = record.supported_reasoning_levels\n const efforts = Array.isArray(levels)\n ? canonicalEfforts(levels.map((entry) => (entry as { effort?: unknown })?.effort ?? entry))\n : undefined\n return { id, displayName, ...(contextWindow !== undefined ? { contextWindow } : {}), ...(maxTokens !== undefined ? { maxTokens } : {}), image, nativeEndpoints, reasoning, ...(efforts !== undefined ? { efforts } : {}) }\n}\n\n/** Decode the full gateway `/v1/models` payload into entries keyed by id. */\nexport function decodeMagpieModels(data: unknown): Map<string, MagpieModelEntry> {\n const result = new Map<string, MagpieModelEntry>()\n if (!data || typeof data !== 'object') return result\n const payload = data as { data?: unknown }\n if (!Array.isArray(payload.data)) return result\n for (const raw of payload.data) {\n const entry = decodeModel(raw)\n if (entry && !result.has(entry.id)) result.set(entry.id, entry)\n }\n return result\n}\n\n/**\n * Compile-time static snapshot (observed 2026-10-02, 22 models): last-resort\n * bootstrap while the gateway is unreachable. Refreshed by hand when the\n * gateway catalog changes shape; live data always wins when reachable.\n */\nexport const staticMagpieModels: MagpieModelEntry[] = [\n { id: 'group/auto-gpt-4-1', displayName: 'GPT-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: [], reasoning: false },\n { id: 'group/auto-gpt-6-luna', displayName: 'GPT-6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: [], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'vercel/xiaomi/mimo-v2.6-flash', displayName: 'xiaomi/mimo-v2.6-flash', contextWindow: 1048576, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true },\n { id: 'vercel/alibaba/qwen3.8-omni-flash', displayName: 'alibaba/qwen3.8-omni-flash', contextWindow: 1000000, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['none', 'low', 'medium', 'xhigh'] },\n { id: 'vercel/openai/gpt-5.4-nano', displayName: 'openai/gpt-5.4-nano', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['none', 'low', 'medium', 'high', 'xhigh'] },\n { id: 'vercel/openai/gpt-4.1', displayName: 'openai/gpt-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: false },\n { id: 'vercel/google/gemini-2.5-flash-lite', displayName: 'google/gemini-2.5-flash-lite', contextWindow: 1048576, maxTokens: 65536, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'medium', 'high'] },\n { id: 'opencode-zen/muse-spark-1.3-contributor-free', displayName: 'Muse Spark 1.3 Free', contextWindow: 1048576, maxTokens: 131072, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['minimal', 'low', 'medium', 'high', 'xhigh'] },\n { id: 'opencode-zen/jev-1.13-free', displayName: 'jev-1.13-free', image: false, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: false },\n { id: 'opencode-zen/mimo-v2.6-flash-free', displayName: 'MiMo-V2.6-Flash Free', contextWindow: 200000, maxTokens: 32000, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: true },\n { id: 'opencode-zen/space-bunny-free', displayName: 'Space Bunny Free', contextWindow: 524288, maxTokens: 524288, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-6-luna', displayName: 'GPT-6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-5.6-terra', displayName: 'GPT-5.6-Terra', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'] },\n { id: 'codex/gpt-5.6-luna', displayName: 'GPT-5.6-Luna', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'codex/gpt-5.5', displayName: 'GPT-5.5', contextWindow: 272000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh'] },\n { id: 'copilot/gemini-3.8-flash', displayName: 'Gemini 3.8 Flash', contextWindow: 1048576, maxTokens: 65536, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'medium', 'high'] },\n { id: 'copilot/grok-4.7', displayName: 'grok-4.7', contextWindow: 500000, maxTokens: 500000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh'] },\n { id: 'copilot/gpt-4.1', displayName: 'gpt-4.1', contextWindow: 1047576, maxTokens: 32768, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/responses', '/v1/messages'], reasoning: false },\n { id: 'copilot/gpt-6-luna', displayName: 'gpt-6-luna', contextWindow: 922000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/responses'], reasoning: true, efforts: ['none', 'low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'copilot/claude-sonnet-5.5', displayName: 'claude-sonnet-5.5', contextWindow: 1000000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions', '/v1/messages'], reasoning: true, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] },\n { id: 'workbuddy-ai/hy4-preview-f', displayName: 'hy4-preview-f', contextWindow: 100000, maxTokens: 64000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['high'] },\n { id: 'workbuddy-ai/deepseek-v4.1-flash', displayName: 'deepseek-v4.1-flash', contextWindow: 100000, maxTokens: 128000, image: true, nativeEndpoints: ['/v1/chat/completions'], reasoning: true, efforts: ['low', 'high', 'max'] },\n]\n\nexport interface CatalogSnapshot {\n status: 'pending' | 'ready' | 'stale' | 'error'\n total: number\n exposed: number\n lastRefresh?: string\n}\n\nexport interface CatalogOptions {\n /** Refresh cadence (seconds). */\n refreshSeconds?: number\n /** Where the gateway snapshot cache lives (plugin data dir). */\n cachePath?: string\n /** Versioned API root override for tests. */\n baseUrl?: string\n /**\n * Gateway credential, sent as `Authorization: Bearer \u2026` on every catalog\n * fetch. The gateway guards `GET /models` with the same key it guards\n * inference with, so a refresh that omits it is answered 401 and the picker\n * silently stays on the static snapshot.\n */\n apiKey?: string\n fetchImpl?: typeof fetch\n now?: () => number\n /** Observability hook: fired after every refresh round. */\n onRefresh?: (status: CatalogSnapshot, lastError: string) => void\n /**\n * Fired when the exposed model set actually changes. DSH's picker caches\n * one `modelCatalog()` read per Host generation and only re-reads it on\n * `llm/adapters-updated`, so a pure in-memory change (hidden models, a\n * refresh that added or dropped models) is invisible without this.\n */\n onInvalidate?: () => void\n /** Delay between startup retries while the live catalog is empty (default 15s). */\n startupRetryMs?: number\n}\n\nconst FETCH_TIMEOUT_MS = 30_000\n\n/** Live model directory with refresh loop. All state in-memory; only the gateway snapshot persists. */\nexport class ModelCatalog {\n #entries: Map<string, MagpieModelEntry> = new Map()\n #hidden: Set<string> = new Set()\n #updatedAt = 0\n #lastError = ''\n #refreshSeconds: number\n #cachePath?: string\n #baseUrl: string\n #apiKey: string\n #fetch: typeof fetch\n #now: () => number\n #timer: NodeJS.Timeout | null = null\n #stopped = false\n #onRefresh?: (status: CatalogSnapshot, lastError: string) => void\n #onInvalidate?: () => void\n #startupRetryMs: number\n /**\n * Last announced exposed set. The picker caches its catalog read, so an\n * invalidation is only worth a Host event when the exposed ids really\n * moved \u2014 otherwise the refresh loop would emit every 5 minutes.\n */\n #announced = ''\n\n constructor(options: CatalogOptions = {}) {\n this.#refreshSeconds = options.refreshSeconds ?? 300\n this.#cachePath = options.cachePath\n this.#baseUrl = (options.baseUrl ?? '').replace(/\\/+$/, '')\n this.#apiKey = options.apiKey ?? ''\n this.#fetch = options.fetchImpl ?? fetch\n this.#now = options.now ?? Date.now\n this.#onRefresh = options.onRefresh\n this.#onInvalidate = options.onInvalidate\n this.#startupRetryMs = options.startupRetryMs ?? 15_000\n }\n\n /**\n * Start the refresh loop: immediate fetch, fast retries while the live\n * catalog is still empty (the first fetch often races the machine's\n * network coming up), then the normal cadence.\n */\n async start(): Promise<void> {\n await this.refreshOnce()\n let attempts = 0\n while (this.#entries.size === 0 && attempts < 4 && !this.#stopped) {\n attempts += 1\n await new Promise((resolve) => setTimeout(resolve, this.#startupRetryMs))\n if (this.#stopped) return\n await this.refreshOnce()\n }\n if (this.#stopped) return\n this.#timer = setInterval(() => {\n void this.refreshOnce()\n }, this.#refreshSeconds * 1000)\n this.#timer.unref?.()\n }\n\n stop(): void {\n this.#stopped = true\n if (this.#timer) {\n clearInterval(this.#timer)\n this.#timer = null\n }\n }\n\n async refreshOnce(): Promise<void> {\n await this.refreshModels()\n if (this.#onRefresh) {\n try {\n this.#onRefresh(this.snapshot(), this.#lastError)\n } catch {\n // observers must never break the refresh loop\n }\n }\n // A refresh can add or drop models: tell the picker to re-read.\n this.#announce()\n }\n\n /**\n * Announce the exposed set when it changed.\n *\n * DSH's browser-side model directory caches one `modelCatalog()` read per\n * Host generation and only re-reads it on `llm/adapters-updated`, so\n * in-memory catalog mutations (hidden models, a refresh that moved the\n * list) are otherwise invisible until restart. Firing the event on every\n * refresh would also work but wakes the whole picker every 5 minutes, so\n * the exposed ids are fingerprinted and only a real change is announced.\n */\n #announce(): void {\n const fingerprint = this.#fingerprint()\n if (fingerprint === this.#announced) return\n this.#announced = fingerprint\n if (!this.#onInvalidate) return\n try {\n this.#onInvalidate()\n } catch {\n // observers must never break the refresh loop\n }\n }\n\n /** Stable fingerprint of the exposed set (ids + their capabilities). */\n #fingerprint(): string {\n const rows = this.list().map((id) => {\n const entry = this.#entryFor(id)\n return [\n id,\n entry?.displayName ?? '',\n entry?.image === true ? 'img' : '-',\n entry?.reasoning === true ? 'think' : '-',\n (entry?.efforts ?? []).join(','),\n String(entry?.contextWindow ?? 0),\n String(entry?.maxTokens ?? 0),\n ].join('|')\n })\n return rows.join('\\n')\n }\n\n /** Whether a gateway API root is configured (empty baseUrl = not configured). */\n get configured(): boolean {\n return this.#baseUrl !== ''\n }\n\n /**\n * Auth header for every catalog fetch. The gateway guards `GET /models` with\n * the same Bearer key as inference, so omitting it is answered 401 \u2014 which\n * the refresh loop then reports as a \"catalog issue\" while the picker keeps\n * serving whatever it already had.\n */\n #authHeaders(): Record<string, string> {\n return this.#apiKey === '' ? {} : { authorization: `Bearer ${this.#apiKey}` }\n }\n\n async refreshModels(): Promise<void> {\n if (!this.configured) {\n this.#lastError = 'magpie gateway API URL or key is not configured \u2014 set both on the Magpie settings page'\n return\n }\n try {\n const entries = await fetchMagpieModels(this.#baseUrl, this.#fetch, { headers: this.#authHeaders() })\n this.#entries = entries\n this.#updatedAt = this.#now()\n this.#lastError = ''\n if (this.#cachePath) await saveModelsCache(this.#cachePath, entries, this.#now()).catch(() => {})\n } catch (err) {\n // Network failure with a cached copy is not fatal: load the cache.\n if (this.#cachePath && this.#entries.size === 0) {\n const cached = await loadModelsCache(this.#cachePath).catch(() => null)\n if (cached && cached.size > 0) {\n this.#entries = cached\n this.#updatedAt = this.#updatedAt === 0 ? this.#now() : this.#updatedAt\n return\n }\n }\n this.#lastError = err instanceof Error ? err.message : String(err)\n }\n }\n\n getEntry(model: string): MagpieModelEntry | undefined {\n return this.#entries.get(model)\n }\n\n /**\n * Replace the settings-page hidden set (models excluded from the picker).\n * Announces: hiding/showing models changes the exposed list, and DSH's\n * picker only re-reads it on `llm/adapters-updated`.\n */\n setHidden(ids: readonly string[]): void {\n const next = new Set(ids)\n if (next.size === this.#hidden.size && [...next].every((id) => this.#hidden.has(id))) return\n this.#hidden = next\n this.#announce()\n }\n\n /** Currently hidden model ids. */\n hidden(): string[] {\n return [...this.#hidden]\n }\n\n /**\n * Drop hidden ids the gateway no longer serves.\n *\n * The hidden set is only meaningful against a live directory: keeping an id\n * that has since left `GET /models` would make the settings page carry rows\n * nobody can act on, and a model that later returns would come back\n * invisibly hidden for no reason the user can see. Called on save, when the\n * directory is the freshest the session has.\n * @param ids - the hidden set as submitted.\n * @returns the subset still worth storing.\n */\n pruneHidden(ids: readonly string[]): string[] {\n if (!this.configured || this.#entries.size === 0) return [...ids]\n return ids.filter((id) => this.#entries.has(id))\n }\n\n isHidden(model: string): boolean {\n return this.#hidden.has(model)\n }\n\n /**\n * Point the refresh loop at another API root or credential (settings page\n * change).\n *\n * The key is part of the identity here, not just the URL: the gateway's\n * `GET /models` is key-guarded, so a key-only edit \u2014 the common case of\n * pasting a fresh key \u2014 must re-read the directory too. When only the key\n * moves the live entries are kept and merely re-fetched; when the root\n * moves they are dropped, because they describe a different gateway.\n * @param baseUrl - the versioned API root ('' = unconfigured).\n * @param apiKey - the gateway credential sent as a Bearer header.\n */\n setEndpoint(baseUrl: string, apiKey: string): void {\n const root = baseUrl.replace(/\\/+$/, '')\n const key = apiKey\n if (root === this.#baseUrl && key === this.#apiKey) return\n const rootChanged = root !== this.#baseUrl\n this.#baseUrl = root\n this.#apiKey = key\n if (rootChanged) {\n // Drop entries from the previous root: the picker must not serve a\n // stale directory, and clearing unconfigured state must empty it.\n this.#entries = new Map()\n this.#updatedAt = 0\n }\n this.#lastError = root === '' ? 'magpie gateway API URL or key is not configured \u2014 set both on the Magpie settings page' : ''\n // The picker must not keep serving the previous endpoint's directory.\n this.#announce()\n }\n\n /** Point the refresh loop at another API root (keeps the current key). */\n setBaseUrl(baseUrl: string): void {\n this.setEndpoint(baseUrl, this.#apiKey)\n }\n\n get baseUrl(): string {\n return this.#baseUrl\n }\n\n get apiKey(): string {\n return this.#apiKey\n }\n\n decision(model: string): { allowed: boolean; source: string; known: boolean } {\n if (!this.configured) return { allowed: false, source: 'unconfigured', known: false }\n if (this.#hidden.has(model)) return { allowed: false, source: 'hidden_by_settings', known: true }\n if (this.#entries.has(model)) return { allowed: true, source: 'gateway', known: true }\n const fallback = staticMagpieModels.find((entry) => entry.id === model)\n if (fallback) return { allowed: true, source: 'static_verified', known: false }\n return { allowed: false, source: this.#entries.size === 0 ? 'gateway_pending' : 'gateway_unknown', known: false }\n }\n\n /** Entry lookup: live map first, static snapshot while configured (never unconfigured). */\n #entryFor(model: string): MagpieModelEntry | undefined {\n if (!this.configured) return undefined\n return this.#entries.get(model) ?? staticMagpieModels.find((candidate) => candidate.id === model)\n }\n\n /**\n * Reasoning capability for one model id (DSH `resolveModel().reasoning`\n * + pi-ai `thinkingLevelMap` source of truth). Ladder-less thinking models\n * (reasoning without an effort list) think with upstream defaults \u2014 see\n * `thinks()` \u2014 and expose no picker.\n */\n reasoningFor(model: string): { efforts: ReasoningEffortChoice[]; defaultEffort: string } | undefined {\n const entry = this.#entryFor(model)\n const efforts = entry?.efforts\n if (!entry?.reasoning) return undefined\n if (efforts !== undefined && efforts.length > 0) {\n const mid = efforts[Math.floor(efforts.length / 2)] ?? 'medium'\n return { efforts: effortsFor(efforts), defaultEffort: efforts.includes('medium') ? 'medium' : mid }\n }\n return undefined\n }\n\n /** Whether the wire model may think (pi-ai `reasoning: true`). */\n thinks(model: string): boolean {\n const entry = this.#entryFor(model)\n return entry?.reasoning ?? false\n }\n\n /** Whether the model accepts image input. */\n supportsImage(model: string): boolean {\n const entry = this.#entryFor(model)\n return entry?.image ?? false\n }\n\n /** Whether one effort id is selectable for the model. */\n supportsEffort(model: string, effort: string): boolean {\n const entry = this.#entryFor(model)\n if (!entry?.reasoning) return false\n if (!entry.efforts || entry.efforts.length === 0) return true // ladder-less: upstream accepts, picker hidden\n return entry.efforts.includes(effort)\n }\n\n requiresResponsesApi(model: string): boolean {\n const entry = this.#entryFor(model)\n if (!entry) return /muse-spark|codex\\/|grok/i.test(model)\n return requiresResponsesApiEntry(entry.nativeEndpoints)\n }\n\n contextWindowFor(model: string): number {\n return this.#entryFor(model)?.contextWindow ?? 262144\n }\n\n maxTokensFor(model: string): number {\n return this.#entryFor(model)?.maxTokens ?? 32768\n }\n\n /** ids exposed to DSH: live gateway list minus hidden (empty while unconfigured). */\n list(): string[] {\n if (!this.configured) return []\n if (this.#entries.size === 0) return staticMagpieModels.map((entry) => entry.id).filter((id) => !this.#hidden.has(id))\n return [...this.#entries.keys()].filter((id) => !this.#hidden.has(id)).sort()\n }\n\n /** Catalog health snapshot (pending/ready/stale + counts). */\n snapshot(): CatalogSnapshot {\n const age = this.#updatedAt === 0 ? Infinity : this.#now() - this.#updatedAt\n const stale = this.#updatedAt !== 0 && age > 10 * 60 * 1000\n return {\n status: this.#updatedAt === 0 ? 'pending' : stale ? 'stale' : 'ready',\n total: this.#entries.size,\n exposed: this.list().length,\n ...(this.#updatedAt !== 0 ? { lastRefresh: new Date(this.#updatedAt).toISOString() } : {}),\n }\n }\n\n get lastError(): string {\n return this.#lastError\n }\n}\n\nasync function withTimeout(\n run: (signal: AbortSignal) => Promise<Response>,\n timeoutMs = FETCH_TIMEOUT_MS,\n): Promise<Response> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await run(controller.signal)\n } catch (error) {\n if (controller.signal.aborted) throw new Error(`models endpoint timed out after ${timeoutMs}ms`)\n throw error\n } finally {\n clearTimeout(timer)\n }\n}\n\n/**\n * Fetch the live gateway model list.\n *\n * `baseUrl` is the versioned API root (`http://127.0.0.1:3425/v1`) exactly as the user\n * configured it; only the resource is appended. Never synthesize a version\n * here \u2014 a gateway on `/v2` would otherwise be asked for `/v2/v1/models`.\n */\nexport async function fetchMagpieModels(\n baseUrl: string,\n fetchImpl: typeof fetch,\n options: { timeoutMs?: number; headers?: Record<string, string> } = {},\n): Promise<Map<string, MagpieModelEntry>> {\n const response = await withTimeout(\n (signal) =>\n fetchImpl(`${baseUrl.replace(/\\/+$/, '')}/models`, {\n headers: { accept: 'application/json', ...(options.headers ?? {}) },\n signal,\n }),\n options.timeoutMs ?? FETCH_TIMEOUT_MS,\n )\n if (!response.ok) throw new Error(`models endpoint returned HTTP ${response.status}`)\n const payload = (await response.json()) as unknown\n const models = decodeMagpieModels(payload)\n if (models.size === 0) throw new Error('models endpoint returned an empty list')\n return models\n}\n\ninterface ModelsCache {\n updatedAt: number\n entries: MagpieModelEntry[]\n}\n\nasync function saveModelsCache(path: string, entries: Map<string, MagpieModelEntry>, now: number): Promise<void> {\n const cache: ModelsCache = { updatedAt: now, entries: [...entries.values()] }\n const tmp = `${path}.${process.pid}.tmp`\n await mkdir(dirname(path), { recursive: true })\n await writeFile(tmp, JSON.stringify(cache), 'utf8')\n await rm(path, { force: true })\n await rename(tmp, path)\n}\n\nasync function loadModelsCache(path: string): Promise<Map<string, MagpieModelEntry>> {\n const raw = JSON.parse(await readFile(path, 'utf8')) as ModelsCache\n if (Date.now() - raw.updatedAt > 7 * 24 * 60 * 60 * 1000) {\n throw new Error('gateway cache too old')\n }\n return new Map(raw.entries.map((entry) => [entry.id, entry]))\n}\n\n/** Default cache location next to the plugin data dir (index.ts convention). */\nexport function defaultCachePath(dataDir: string): string {\n return join(dataDir, 'magpie-models.json')\n}\n", "import { createProvider, type Api, type Context, type Model } from '@earendil-works/pi-ai'\nimport * as openaiCompletions from '@earendil-works/pi-ai/api/openai-completions'\nimport * as openaiResponses from '@earendil-works/pi-ai/api/openai-responses'\n\nimport {\n ModelCatalog,\n modelApiForEntry,\n thinkingLevelMapFor,\n} from './catalog.ts'\nimport { toStreamChunks, type HarnessChunk, type PiEvent } from './events.ts'\nimport {\n contentHasImage,\n toPiContext,\n toPiContextWithImages,\n type AttachmentStore,\n type HarnessGenerateOptions,\n} from './messages.ts'\nimport { withStallTimeout } from './watchdog.ts'\n\n/**\n * Magpie LAN gateway adapter: registers as a DSH LlmAdapter and streams\n * directly from the configured API root (e.g. `\u2026/v1`) \u2014 chat completions for most models plus the\n * Responses API for responses-only models (Muse Spark / Codex / Grok lanes).\n *\n * Adapter contract: dsh-llm LlmAdapter (providerInfo/providerRetryPolicy/\n * imageRequestPricing/listModels/resolveModel/prepareCall/stream) \u2014\n * structural, no host import.\n *\n * Images resolve through the harness attachment service; thinking levels come\n * from the gateway catalog and forward verbatim (including the `none`/`ultra`\n * extensions pi-ai does not model).\n */\n\n/** Structural pi-ai provider surface the adapter consumes. */\nexport interface PiProviderLike {\n stream(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>\n streamSimple(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>\n}\n\n/** Live endpoint read per request so the settings page applies without restart. */\nexport interface RuntimeEndpoint {\n /** Versioned API root, e.g. `http://127.0.0.1:3425/v1` (no trailing slash). */\n baseUrl(): string\n /** Bearer key sent to the gateway. */\n apiKey(): string\n}\n\nexport const PROVIDER_ID = 'dsh-magpie-connect'\n\n/** Default picker label for the default route id. */\nexport const DEFAULT_DISPLAY_NAME = 'Magpie'\n\nexport interface MagpieModelInfo {\n id: string\n name: string\n contextWindow: number\n maxTokens: number\n}\n\nexport interface CatalogLike {\n list(): string[]\n decision(model: string): { allowed: boolean; source: string; known: boolean }\n getEntry?(model: string): { nativeEndpoints: string[] } | undefined\n reasoningFor?(model: string): { efforts: Array<{ id: string; name: string; description?: string }>; defaultEffort: string } | undefined\n thinks?(model: string): boolean\n supportsImage?(model: string): boolean\n supportsEffort?(model: string, effort: string): boolean\n requiresResponsesApi?(model: string): boolean\n contextWindowFor?(model: string): number\n maxTokensFor?(model: string): number\n}\n\nconst DEFAULT_CONTEXT_WINDOW = 262144\nconst DEFAULT_MAX_TOKENS = 32768\n\n/** Credential sent to the LAN gateway (it needs none; pi-ai requires a value). */\nconst DEFAULT_API_KEY = 'not-needed'\n\n/** Connection-setup retries (429/5xx with backoff, interruptible by abort). */\nexport const DEFAULT_MAX_RETRIES = 2\n/** Overall SDK request cap (replaces the OpenAI SDK 10 min default). */\nexport const DEFAULT_TIMEOUT_MS = 300_000\n/** Max wait for the first upstream event (queueing happens here). */\nexport const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 90_000\n/** Max silence between upstream events once streaming. */\nexport const DEFAULT_IDLE_TIMEOUT_MS = 60_000\n\nconst STRENGTH_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'] as const\n\n/**\n * Plan the wire `reasoning_effort` for one request. Returns the effort to\n * send via `provider.stream`, or undefined to omit the field (gateway\n * default applies).\n *\n * - `off` aliases `none` (explicitly disable thinking).\n * - `none` sends only when the model lists it; otherwise omitted.\n * - `ultra` sends when listed; otherwise degrades to the highest listed\n * strength level (`max` \u2192 \u2026 \u2192 `minimal`).\n * - Known strength levels send verbatim when listed or when the model thinks\n * without a ladder (gateway decides); otherwise clamped up-then-down to\n * the nearest listed level so a stale remembered value never 400s.\n * - Unknown values and non-reasoning models omit.\n */\nexport function planReasoningEffort(\n requested: unknown,\n model: string,\n catalog: Pick<CatalogLike, 'thinks' | 'supportsEffort' | 'reasoningFor'>,\n): string | undefined {\n if (typeof requested !== 'string') return undefined\n const level = requested.trim().toLowerCase()\n if (level === '') return undefined\n const thinks = catalog.thinks?.(model) ?? false\n if (!thinks) return undefined\n const normalized = level === 'off' ? 'none' : level\n const supports = (effort: string): boolean => catalog.supportsEffort?.(model, effort) ?? false\n const ladder = catalog.reasoningFor?.(model)?.efforts.map((effort) => effort.id)\n const ladderLess = ladder === undefined\n\n if (normalized === 'none') {\n return supports('none') ? 'none' : undefined\n }\n if (!(STRENGTH_ORDER as readonly string[]).includes(normalized)) return undefined\n if (supports(normalized) || ladderLess) return normalized\n // Clamp a stale/foreign level up-then-down (pi-ai clamp order).\n const order = STRENGTH_ORDER as readonly string[]\n const index = order.indexOf(normalized as (typeof STRENGTH_ORDER)[number])\n if (index !== -1) {\n for (let i = index + 1; i < order.length; i++) {\n const candidate = order[i] as string\n if (supports(candidate)) return candidate\n }\n for (let i = index - 1; i >= 0; i--) {\n const candidate = order[i] as string\n if (supports(candidate)) return candidate\n }\n }\n return undefined\n}\n\nfunction toPiModel(\n providerId: string,\n baseUrl: string,\n id: string,\n catalog: CatalogLike,\n contextWindow = DEFAULT_CONTEXT_WINDOW,\n maxTokens = DEFAULT_MAX_TOKENS,\n): Model<Api> {\n const endpoints = catalog.getEntry?.(id)?.nativeEndpoints ?? []\n const api = modelApiForEntry(endpoints)\n const entryThinks = catalog.thinks?.(id) ?? api === 'openai-responses'\n const ladder = catalog.reasoningFor?.(id)\n const ladderIds = ladder?.efforts.map((effort) => effort.id)\n const resolvedContext = catalog.contextWindowFor?.(id) ?? contextWindow\n const resolvedMax = catalog.maxTokensFor?.(id) ?? maxTokens\n if (api === 'openai-responses') {\n return {\n id,\n name: id,\n api,\n provider: providerId,\n baseUrl,\n reasoning: true,\n thinkingLevelMap: thinkingLevelMapFor(ladderIds),\n input: catalog.supportsImage?.(id) ?? true ? ['text', 'image'] : ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: resolvedContext,\n maxTokens: resolvedMax,\n }\n }\n const wire: Model<Api> = {\n id,\n name: id,\n api,\n provider: providerId,\n baseUrl,\n reasoning: entryThinks,\n input: catalog.supportsImage?.(id) ?? false ? ['text', 'image'] : ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: resolvedContext,\n maxTokens: resolvedMax,\n }\n if (entryThinks) wire.thinkingLevelMap = thinkingLevelMapFor(ladderIds)\n return wire\n}\n\nexport class MagpieAdapter {\n readonly #catalog: CatalogLike\n readonly #provider: PiProviderLike\n readonly #providerId: string\n readonly #displayName: string\n /** Static fallback API root; the live settings page overrides per request. */\n readonly #fallbackBaseUrl: string\n readonly #fallbackApiKey: string\n readonly #runtime?: RuntimeEndpoint\n readonly #maxRetries: number\n readonly #timeoutMs: number\n readonly #firstEventTimeoutMs: number\n readonly #idleTimeoutMs: number\n readonly #resolveAttachments?: () => AttachmentStore | undefined\n\n constructor(\n catalog: CatalogLike,\n options: {\n magpieBaseUrl?: string\n baseUrl?: string\n providerOverride?: unknown\n /** Route id registered into DSH (model `provider` field + pi-ai provider tag). */\n providerId?: string\n /** Display name reported via providerInfo (model picker grouping label). */\n displayName?: string\n /** Gateway credential (LAN needs none). */\n apiKey?: string\n /**\n * Live endpoint read per request (settings page). Falls back to the\n * static `magpieBaseUrl`/`apiKey` options when absent.\n */\n runtime?: RuntimeEndpoint\n /** Connection-setup retries for 429/5xx (default 2). */\n maxRetries?: number\n /** Overall SDK request cap in ms (default 300000). */\n timeoutMs?: number\n /** Stall watchdog: max wait for the first upstream event in ms (default 90000; <=0 disables). */\n firstEventTimeoutMs?: number\n /** Stall watchdog: max silence between upstream events in ms (default 60000; <=0 disables). */\n idleTimeoutMs?: number\n /** Harness attachment service for image bytes (wired in index.ts). */\n resolveAttachments?: () => AttachmentStore | undefined\n } = {},\n ) {\n this.#catalog = catalog\n this.#providerId = options.providerId ?? PROVIDER_ID\n this.#displayName = options.displayName ?? (options.providerId ?? DEFAULT_DISPLAY_NAME)\n // The configured value is already the versioned API root (`\u2026/v1`), which is\n // exactly what pi-ai's `baseUrl` expects: it appends `/chat/completions`\n // and `/responses` itself. Synthesizing a version here would double it and\n // would hard-code `/v1` against a gateway that later moves to `/v2`.\n this.#fallbackBaseUrl = (options.magpieBaseUrl ?? options.baseUrl ?? '').replace(/\\/+$/, '')\n this.#fallbackApiKey = options.apiKey ?? DEFAULT_API_KEY\n this.#runtime = options.runtime\n this.#maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES\n this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS\n this.#firstEventTimeoutMs = options.firstEventTimeoutMs ?? DEFAULT_FIRST_EVENT_TIMEOUT_MS\n this.#idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS\n this.#resolveAttachments = options.resolveAttachments\n if (options.providerOverride !== undefined) {\n this.#provider = options.providerOverride as never\n return\n }\n this.#provider = createProvider<Api>({\n id: this.#providerId,\n name: this.#displayName,\n baseUrl: this.#fallbackBaseUrl,\n auth: {\n apiKey: {\n name: 'Magpie LAN gateway',\n resolve: async () => ({ auth: { apiKey: this.#runtime?.apiKey() ?? this.#fallbackApiKey } }),\n },\n },\n models: [],\n // Mixed-API provider: pi-ai dispatches on `model.api`, so completions\n // models ride /v1/chat/completions while responses-only models ride\n // /v1/responses through the same provider instance.\n api: {\n 'openai-completions': openaiCompletions,\n 'openai-responses': openaiResponses,\n },\n }) as unknown as PiProviderLike\n }\n\n providerInfo(provider: string): { id: string; name: string } {\n return { id: provider, name: this.#displayName }\n }\n\n /**\n * dsh-llm calls this unconditionally at registration.\n * undefined = the host default retry policy.\n */\n providerRetryPolicy(_provider: string): undefined {\n return undefined\n }\n\n /**\n * Per-route visual-token pricing for the token meter (`/compact` calls\n * this unconditionally via `ctx.llm.imageRequestPricing`). The gateway\n * charges no separate visual tokens (pi-ai cost is 0), so like the host\n * base-class default we declare no per-route pricing.\n */\n imageRequestPricing(_provider: string, _model: string): undefined {\n return undefined\n }\n\n /** Advisory catalog for the DSH model picker (deduped; dsh-llm rejects duplicates). */\n listModels(provider: string): Array<{ provider: string; id: string; name: string; inputModalities: string[] }> {\n const seen = new Set<string>()\n const models: Array<{ provider: string; id: string; name: string; inputModalities: string[] }> = []\n for (const id of this.#catalog.list()) {\n if (seen.has(id)) continue\n seen.add(id)\n const image = this.#catalog.supportsImage?.(id) ?? false\n models.push({ provider, id, name: id, inputModalities: image ? ['text', 'image'] : ['text'] })\n }\n return models\n }\n\n resolveModel(\n provider: string,\n model: string,\n ): {\n provider: string\n id: string\n name: string\n inputModalities: string[]\n context: { contextWindow: number }\n defaultMaxTokens: number\n reasoning?: {\n efforts: ReadonlyArray<{ id: string; name: string; description?: string }>\n defaultEffort?: string\n }\n } {\n // DSH capability seam: exposing efforts lights up the session model\n // selector's reasoning picker; ids flow back as\n // GenerateOptions.reasoningEffort and are forwarded verbatim by\n // planReasoningEffort (gateway extensions `none`/`ultra` included).\n // No `off` entry: omission keeps the gateway default (= defaultEffort).\n const image = this.#catalog.supportsImage?.(model) ?? this.#catalog.requiresResponsesApi?.(model) ?? false\n const ladder = this.#catalog.reasoningFor?.(model)\n return {\n provider,\n id: model,\n name: model,\n inputModalities: image ? ['text', 'image'] : ['text'],\n context: { contextWindow: this.#catalog.contextWindowFor?.(model) ?? DEFAULT_CONTEXT_WINDOW },\n defaultMaxTokens: this.#catalog.maxTokensFor?.(model) ?? DEFAULT_MAX_TOKENS,\n ...(ladder !== undefined ? { reasoning: ladder } : {}),\n }\n }\n\n async prepareCall(\n provider: string,\n model: string,\n _signal?: AbortSignal,\n ): Promise<{\n model: ReturnType<MagpieAdapter['resolveModel']>\n stream: (options: HarnessGenerateOptions) => AsyncGenerator<HarnessChunk>\n }> {\n return {\n model: this.resolveModel(provider, model),\n stream: (options) => this.stream(options),\n }\n }\n\n /**\n * Fail fast when no gateway API URL is configured (picker stays empty too).\n * @returns the versioned API root, trailing slash stripped.\n */\n #requireBaseUrl(): string {\n const raw = this.#runtime?.baseUrl() ?? this.#fallbackBaseUrl\n const baseUrl = raw.replace(/\\/+$/, '')\n if (baseUrl === '') {\n throw new Error('dsh-magpie-connect: Magpie gateway API URL or key is not configured \u2014 open Settings \u2192 Magpie and fill in both')\n }\n return baseUrl\n }\n\n /** Effective bearer key for this request. */\n #wireApiKey(): string {\n return this.#runtime?.apiKey() ?? this.#fallbackApiKey\n }\n\n /**\n * Stream one chat turn from the Magpie gateway: a single pi-ai stream,\n * translated to harness chunks verbatim. Upstream failures (rate limit,\n * auth, timeout, transport) arrive as classified finish reasons, and\n * turn-level retries stay owned by DSH.\n *\n * A stall watchdog races every upstream event against a timer: on expiry\n * the upstream is aborted and the turn ends fast with a TIMEOUT error\n * instead of hanging to the SDK/harness timeout.\n */\n async *stream(options: HarnessGenerateOptions): AsyncGenerator<HarnessChunk> {\n const baseUrl = this.#requireBaseUrl()\n const endpoints = this.#catalog.getEntry?.(options.model)?.nativeEndpoints ?? []\n const hasImage = contentHasImage(options.messages)\n if (hasImage && !(this.#catalog.supportsImage?.(options.model) ?? false)) {\n throw new Error(`dsh-magpie-connect: model \"${options.model}\" does not accept image input`)\n }\n const context = hasImage\n ? await this.#imageContext(options, endpoints)\n : toPiContext(options, endpoints)\n const model = toPiModel(this.#providerId, baseUrl, options.model, this.#catalog)\n // Linked controller: the watchdog aborts the upstream on stall, while a\n // harness abort (user stop / host timeout) still propagates through.\n const controller = new AbortController()\n const harnessSignal = options.signal\n const onHarnessAbort = (): void => controller.abort()\n if (harnessSignal) {\n if (harnessSignal.aborted) controller.abort()\n else harnessSignal.addEventListener('abort', onHarnessAbort, { once: true })\n }\n try {\n const events = withStallTimeout(\n this.#eventsFor(options, context, model, controller.signal) as AsyncIterable<PiEvent>,\n { firstEventTimeoutMs: this.#firstEventTimeoutMs, idleTimeoutMs: this.#idleTimeoutMs },\n { model: options.model, onTimeout: () => controller.abort() },\n )\n yield* toStreamChunks(events, model.contextWindow)\n } finally {\n harnessSignal?.removeEventListener('abort', onHarnessAbort)\n }\n }\n\n async #imageContext(options: HarnessGenerateOptions, endpoints: readonly string[]): Promise<ReturnType<typeof toPiContext>> {\n const attachments = this.#resolveAttachments?.()\n if (!attachments) {\n throw new Error('dsh-magpie-connect: image input requires the attachment service')\n }\n return toPiContextWithImages(options, attachments, endpoints)\n }\n\n #eventsFor(\n options: HarnessGenerateOptions,\n context: ReturnType<typeof toPiContext>,\n model: ReturnType<typeof toPiModel>,\n signal: AbortSignal,\n ): unknown {\n // Single dispatch through `provider.stream` with our own clamped effort:\n // gateway extensions (`none`/`ultra`) ride verbatim, known levels ride\n // the ladder, and omission keeps the gateway default.\n const reasoningEffort = planReasoningEffort(options.reasoningEffort, options.model, this.#catalog)\n return this.#provider.stream(model, context as unknown as Context, {\n apiKey: this.#wireApiKey(),\n signal,\n maxRetries: this.#maxRetries,\n timeoutMs: this.#timeoutMs,\n temperature: options.temperature,\n maxTokens: options.maxTokens ?? model.maxTokens,\n ...(reasoningEffort !== undefined ? { reasoningEffort } : {}),\n })\n }\n\n /** Expose the live catalog snapshot for diagnostics. */\n catalogStatus(): { total: number; exposed: number } {\n const list = this.#catalog.list()\n return { total: list.length, exposed: list.length }\n }\n\n decisionFor(model: string): { allowed: boolean; source: string } {\n const decision = this.#catalog.decision(model)\n return { allowed: decision.allowed, source: decision.source }\n }\n}\n\n/** Build the adapter over a live catalog. */\nexport function createMagpieAdapter(catalog: ModelCatalog): MagpieAdapter {\n return new MagpieAdapter(catalog)\n}\n", "/**\n * pi-ai AssistantMessageEvent -> harness StreamChunks.\n * The chunk stream must end with `usage` then `finish`.\n */\n\nexport type HarnessChunk =\n | { type: 'block-start'; index: number; blockType: 'text' | 'reasoning' | 'tool-call' }\n | { type: 'text-delta'; index: number; text: string }\n | { type: 'block-end'; index: number; block: { type: 'text'; text: string } | { type: 'reasoning'; text: string } | { type: 'tool-call'; id: string; name: string; arguments: string } }\n | { type: 'reasoning-delta'; index: number; text: string }\n | { type: 'tool-call-delta'; index: number; id: string; name?: string; argumentsDelta: string }\n | { type: 'usage'; usage: { inputTokens: number; outputTokens: number; cacheReadTokens?: number; cacheWriteTokens?: number } }\n | { type: 'finish'; reason: FinishReason; replayState?: unknown }\n\nexport type FinishReason =\n | { kind: 'stop' }\n | { kind: 'max-tokens' }\n | { kind: 'tool-calls' }\n | { kind: 'aborted'; failure: { message: string; code: string } }\n | { kind: 'error'; failure: { message: string; code: string } }\n\n/** pi-ai AssistantMessageEvent vocabulary (subset we consume). */\nexport type PiEvent =\n | { type: 'start'; partial: PiAssistantPartial }\n | { type: 'text_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'text_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'text_end'; contentIndex: number; content: string; partial: PiAssistantPartial }\n | { type: 'thinking_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'thinking_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'thinking_end'; contentIndex: number; content: string; partial: PiAssistantPartial }\n | { type: 'toolcall_start'; contentIndex: number; partial: PiAssistantPartial }\n | { type: 'toolcall_delta'; contentIndex: number; delta: string; partial: PiAssistantPartial }\n | { type: 'toolcall_end'; contentIndex: number; toolCall: { id: string; name: string; arguments: Record<string, unknown> }; partial: PiAssistantPartial }\n | { type: 'done'; message: PiDoneMessage }\n | { type: 'error'; error: PiDoneMessage }\n\nexport interface PiAssistantPartial {\n content: Array<{ type: string; id?: string; name?: string; [key: string]: unknown }>\n [key: string]: unknown\n}\n\n/** pi-ai AssistantMessage content block (done/error messages carry these). */\nexport type PiAssistantBlock = { type: string; [key: string]: unknown }\n\nexport interface PiDoneMessage {\n api: string\n provider: string\n model: string\n responseModel?: string\n responseId?: string\n content: PiAssistantBlock[]\n usage: { input: number; output: number; cacheRead: number; cacheWrite: number; totalTokens: number }\n stopReason: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted'\n errorMessage?: string\n [key: string]: unknown\n}\n\nconst CONTEXT_WINDOW_EXCEEDED = 'CONTEXT_WINDOW_EXCEEDED'\nconst EMPTY_RESPONSE = 'EMPTY_RESPONSE'\nconst QUOTA_EXCEEDED = 'QUOTA_EXCEEDED'\n\nfunction classifyError(text: string): string {\n if (/\\b(?:401|403)\\b/.test(text)) return 'AUTH'\n if (/insufficient|quota|billing/i.test(text)) return QUOTA_EXCEEDED\n if (/\\b429\\b|rate.?limit/i.test(text)) return 'RATE_LIMIT'\n if (/\\b413\\b|payload too large|request body too large/i.test(text)) return 'INVALID_REQUEST'\n if (/\\b400\\b|invalid.?request/i.test(text)) return 'INVALID_REQUEST'\n if (/\\b5\\d\\d\\b/.test(text)) return 'SERVER'\n if (/\\btime(?:d)?\\s*out\\b|timeout/i.test(text)) return 'TIMEOUT'\n if (/\\b(?:network|connection|socket|fetch)\\b|\\bECONN[A-Z]+\\b|terminated|premature close/i.test(text)) return 'TRANSPORT'\n return 'UPSTREAM'\n}\n\nfunction isContextOverflow(message: PiDoneMessage, contextWindow: number): boolean {\n // pi-ai isContextOverflow equivalent: error-free finish whose input usage\n // alone exceeds the resolved window.\n return message.stopReason === 'stop' && message.usage.input > contextWindow\n}\n\n/** Map a pi-ai terminal message to a harness finish reason. */\nfunction mapStopReason(message: PiDoneMessage, contextWindow: number): FinishReason {\n if (isContextOverflow(message, contextWindow) || (message.stopReason === 'error' && message.errorMessage !== undefined && /context/i.test(message.errorMessage) && /exceed|window|length|token/i.test(message.errorMessage))) {\n return {\n kind: 'error',\n failure: {\n message: message.errorMessage ?? `pi-ai detected context overflow for model \"${message.model}\"`,\n code: CONTEXT_WINDOW_EXCEEDED,\n },\n }\n }\n switch (message.stopReason) {\n case 'stop':\n if (message.content.length === 0) {\n return {\n kind: 'error',\n failure: { message: `model \"${message.model}\" returned a completed response with no content`, code: EMPTY_RESPONSE },\n }\n }\n return { kind: 'stop' }\n case 'length':\n return { kind: 'max-tokens' }\n case 'toolUse':\n return { kind: 'tool-calls' }\n case 'aborted':\n return { kind: 'aborted', failure: { message: message.errorMessage ?? 'pi-ai stream aborted', code: 'ABORTED' } }\n case 'error':\n return {\n kind: 'error',\n failure: { message: message.errorMessage ?? 'pi-ai stream error', code: classifyError(message.errorMessage ?? '') },\n }\n }\n}\n\nfunction mapUsage(usage: PiDoneMessage['usage']): Extract<HarnessChunk, { type: 'usage' }>['usage'] {\n return {\n inputTokens: usage.input,\n outputTokens: usage.output,\n ...(usage.cacheRead > 0 ? { cacheReadTokens: usage.cacheRead } : {}),\n ...(usage.cacheWrite > 0 ? { cacheWriteTokens: usage.cacheWrite } : {}),\n }\n}\n\n/**\n * Translate one pi-ai event stream into harness chunks. pi-ai never throws\n * mid-stream: failures arrive as `error` events and become error/aborted\n * finish chunks.\n */\nexport async function* toStreamChunks(events: AsyncIterable<PiEvent>, contextWindow: number): AsyncGenerator<HarnessChunk> {\n const toolIds = new Map<number, { id: string; name: string }>()\n for await (const event of events) {\n switch (event.type) {\n case 'start':\n break\n case 'text_start':\n yield { type: 'block-start', index: event.contentIndex, blockType: 'text' }\n break\n case 'text_delta':\n yield { type: 'text-delta', index: event.contentIndex, text: event.delta }\n break\n case 'text_end':\n yield { type: 'block-end', index: event.contentIndex, block: { type: 'text', text: event.content } }\n break\n case 'thinking_start':\n yield { type: 'block-start', index: event.contentIndex, blockType: 'reasoning' }\n break\n case 'thinking_delta':\n yield { type: 'reasoning-delta', index: event.contentIndex, text: event.delta }\n break\n case 'thinking_end':\n yield { type: 'block-end', index: event.contentIndex, block: { type: 'reasoning', text: event.content } }\n break\n case 'toolcall_start': {\n const partial = event.partial.content[event.contentIndex]\n const id = partial?.type === 'toolCall' ? (partial.id ?? '') : ''\n const name = partial?.type === 'toolCall' ? (partial.name ?? '') : ''\n toolIds.set(event.contentIndex, { id, name })\n yield { type: 'block-start', index: event.contentIndex, blockType: 'tool-call' }\n break\n }\n case 'toolcall_delta': {\n const known = toolIds.get(event.contentIndex)\n yield {\n type: 'tool-call-delta',\n index: event.contentIndex,\n id: known?.id ?? '',\n ...(known?.name !== undefined && known.name.length > 0 ? { name: known.name } : {}),\n argumentsDelta: event.delta,\n }\n break\n }\n case 'toolcall_end':\n yield {\n type: 'block-end',\n index: event.contentIndex,\n block: {\n type: 'tool-call',\n id: event.toolCall.id,\n name: event.toolCall.name,\n arguments: JSON.stringify(event.toolCall.arguments),\n },\n }\n break\n case 'done':\n yield { type: 'usage', usage: mapUsage(event.message.usage) }\n yield { type: 'finish', reason: mapStopReason(event.message, contextWindow) }\n return\n case 'error':\n yield { type: 'usage', usage: mapUsage(event.error.usage) }\n yield { type: 'finish', reason: mapStopReason(event.error, contextWindow) }\n return\n }\n }\n throw new Error('dsh-magpie-connect: pi-ai event stream ended without done/error')\n}\n", "/**\n * Harness GenerateOptions -> pi-ai Context conversion, with image support.\n *\n * Text-only requests convert synchronously (`toPiContext`); requests carrying\n * durable image blocks resolve bytes through the harness attachment service\n * (`toPiContextWithImages`, async). pi-ai then encodes images as OpenAI\n * `image_url` (completions) or `input_image` (responses) parts.\n */\n\nimport { modelApiForEntry } from './catalog.ts'\n\nexport interface ImageAttachmentRef {\n attachmentId: string\n mediaType: string\n bytes: number\n width: number\n height: number\n name?: string\n}\n\nexport interface FileAttachmentRef {\n attachmentId: string\n name: string\n bytes: number\n}\n\nexport interface HarnessTool {\n name: string\n description: string\n parameters: unknown\n}\n\nexport type HarnessBlock =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool-call'; id: string; name: string; arguments: string }\n | { type: 'image'; attachment: ImageAttachmentRef; offloaded?: true; [key: string]: unknown }\n | { type: 'file'; attachment: FileAttachmentRef; [key: string]: unknown }\n | { type: 'tool-result'; toolCallId: string; content: HarnessBlock[]; isError?: boolean; [key: string]: unknown }\n | { type: string; [key: string]: unknown }\n\nexport interface HarnessMessage {\n role: 'system' | 'user' | 'assistant' | 'tool' | 'developer'\n content: HarnessBlock[]\n source?: { kind: string; provider?: string; model?: string; callId?: string; [key: string]: unknown }\n toolCallId?: string\n isError?: boolean\n [key: string]: unknown\n}\n\nexport interface HarnessGenerateOptions {\n provider: string\n model: string\n messages: HarnessMessage[]\n system?: string\n tools?: HarnessTool[]\n maxTokens?: number\n temperature?: number\n reasoningEffort?: string\n signal?: AbortSignal\n [key: string]: unknown\n}\n\n/** pi-ai message vocabulary (subset we emit). */\nexport type PiMessage =\n | { role: 'user'; content: string | PiContentBlock[]; timestamp: number }\n | {\n role: 'assistant'\n content: PiAssistantBlock[]\n api: 'openai-completions' | 'openai-responses'\n provider: string\n model: string\n usage: PiUsage\n stopReason: 'stop' | 'toolUse'\n timestamp: number\n }\n | { role: 'toolResult'; toolCallId: string; toolName: string; content: PiContentBlock[]; isError: boolean; timestamp: number }\n\nexport type PiAssistantBlock =\n | { type: 'text'; text: string }\n | { type: 'thinking'; thinking: string }\n | { type: 'toolCall'; id: string; name: string; arguments: Record<string, unknown> }\n\nexport type PiContentBlock = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }\n\nexport interface PiUsage {\n input: number\n output: number\n cacheRead: number\n cacheWrite: number\n totalTokens: number\n cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number }\n}\n\nexport interface PiTool {\n name: string\n description: string\n parameters: unknown\n}\n\nexport interface PiContext {\n systemPrompt?: string\n messages: PiMessage[]\n tools?: PiTool[]\n}\n\n/** Attachment service surface we consume (structural subset of dsh-attachment). */\nexport interface AttachmentStore {\n readImageRequest(\n ref: ImageAttachmentRef,\n target: { width: number; height: number; maxBytes: number },\n signal?: AbortSignal,\n ): Promise<{ data: Uint8Array; mediaType: string; bytes: number; width: number; height: number }>\n}\n\nexport interface RequestImageVersion {\n data: Uint8Array\n mediaType: string\n bytes: number\n width: number\n height: number\n}\n\nconst REQUEST_MAX_PIXELS = 2048 * 2048\nconst REQUEST_MAX_BYTES = 1024 * 1024\n\nexport function zeroUsage(): PiUsage {\n return {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n }\n}\n\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (typeof raw !== 'string' || raw.length === 0) return {}\n try {\n const parsed = JSON.parse(raw) as unknown\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : { value: parsed }\n } catch {\n return { raw }\n }\n}\n\n/** True when any message carries a retained (non-offloaded) image block. */\nexport function contentHasImage(messages: readonly HarnessMessage[]): boolean {\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type === 'image' && (block as { offloaded?: unknown }).offloaded !== true) return true\n }\n }\n return false\n}\n\n/** Request target for one attachment: fit within the pixel budget, cap bytes. */\nexport function requestImageTarget(ref: ImageAttachmentRef): { width: number; height: number; maxBytes: number } {\n const pixels = ref.width * ref.height\n if (!(pixels > REQUEST_MAX_PIXELS) || !(ref.width > 0 && ref.height > 0)) {\n return { width: ref.width, height: ref.height, maxBytes: REQUEST_MAX_BYTES }\n }\n const scale = Math.sqrt(REQUEST_MAX_PIXELS / pixels)\n return { width: Math.max(1, Math.floor(ref.width * scale)), height: Math.max(1, Math.floor(ref.height * scale)), maxBytes: REQUEST_MAX_BYTES }\n}\n\nfunction imageHandleText(ref: ImageAttachmentRef, version: RequestImageVersion): string {\n const name = ref.name ?? `attachment ${String(ref.attachmentId).slice(0, 12)}`\n return `Image ${name}; request preview ${version.width}x${version.height}px.`\n}\n\nfunction offloadedImageText(ref: ImageAttachmentRef): string {\n const name = ref.name ?? `attachment ${String(ref.attachmentId).slice(0, 12)}`\n return `[image omitted (offloaded); ${name}]`\n}\n\nfunction toBase64(data: Uint8Array): string {\n return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64')\n}\n\n/**\n * Resolve every retained image to request bytes. Offloaded occurrences need\n * no bytes (placeholder text only) and are skipped here.\n */\nexport async function prepareRequestImages(\n messages: readonly HarnessMessage[],\n attachments: AttachmentStore,\n signal?: AbortSignal,\n): Promise<Map<string, RequestImageVersion>> {\n const refs = new Map<string, ImageAttachmentRef>()\n for (const message of messages) {\n for (const block of message.content) {\n if (block.type !== 'image') continue\n const image = block as Extract<HarnessBlock, { type: 'image' }>\n if (image.offloaded === true) continue\n if (!refs.has(image.attachment.attachmentId)) refs.set(image.attachment.attachmentId, image.attachment)\n }\n }\n const versions = new Map<string, RequestImageVersion>()\n await Promise.all(\n [...refs.values()].map(async (ref) => {\n const prepared = await attachments.readImageRequest(ref, requestImageTarget(ref), signal)\n versions.set(ref.attachmentId, {\n data: prepared.data,\n mediaType: prepared.mediaType,\n bytes: prepared.bytes,\n width: prepared.width,\n height: prepared.height,\n })\n }),\n )\n return versions\n}\n\n/** User/tool-result content with images resolved to handle text + bytes. */\nexport function userContentWithImages(\n blocks: HarnessBlock[],\n requestImages: Map<string, RequestImageVersion>,\n): string | PiContentBlock[] {\n const content: PiContentBlock[] = []\n for (const block of blocks) {\n switch (block.type) {\n case 'text':\n if ((block as { text: string }).text.length > 0) content.push({ type: 'text', text: (block as { text: string }).text })\n break\n case 'image': {\n const image = block as Extract<HarnessBlock, { type: 'image' }>\n if (image.offloaded === true) {\n content.push({ type: 'text', text: offloadedImageText(image.attachment) })\n break\n }\n const version = requestImages.get(image.attachment.attachmentId)\n if (!version) {\n content.push({ type: 'text', text: offloadedImageText(image.attachment) })\n break\n }\n content.push({ type: 'text', text: imageHandleText(image.attachment, version) })\n content.push({ type: 'image', data: toBase64(version.data), mimeType: version.mediaType })\n break\n }\n case 'file': {\n const file = block as Extract<HarnessBlock, { type: 'file' }>\n content.push({ type: 'text', text: `[file: ${file.attachment.name} (${file.attachment.bytes} bytes)]` })\n break\n }\n case 'tool-result':\n content.push({ type: 'text', text: toolResultText((block as { content: HarnessBlock[] }).content) || '(no output)' })\n break\n default:\n break\n }\n }\n if (content.every((block) => block.type === 'text')) return content.map((block) => (block as { text: string }).text).join('')\n return content\n}\n\nfunction toPiAssistant(\n message: HarnessMessage,\n providerId: string,\n api: 'openai-completions' | 'openai-responses' = 'openai-completions',\n): Extract<PiMessage, { role: 'assistant' }> {\n const content: PiAssistantBlock[] = []\n for (const block of message.content) {\n switch (block.type) {\n case 'text':\n content.push({ type: 'text', text: (block as { text: string }).text })\n break\n case 'reasoning':\n content.push({ type: 'thinking', thinking: (block as { text: string }).text })\n break\n case 'tool-call': {\n const call = block as { id: string; name: string; arguments: string }\n content.push({ type: 'toolCall', id: call.id, name: call.name, arguments: parseArguments(call.arguments) })\n break\n }\n case 'image':\n throw new Error('dsh-magpie-connect: assistant image output cannot be replayed to the model')\n default:\n break\n }\n }\n const source = message.source\n return {\n role: 'assistant',\n content,\n api,\n provider: source?.kind === 'model' && typeof source.provider === 'string' ? source.provider : providerId,\n model: source?.kind === 'model' && typeof source.model === 'string' ? source.model : providerId,\n usage: zeroUsage(),\n stopReason: content.some((block) => block.type === 'toolCall') ? 'toolUse' : 'stop',\n timestamp: 0,\n }\n}\n\nfunction flattenText(message: HarnessMessage): string {\n return message.content\n .filter((block) => block.type === 'text')\n .map((block) => (block as { text: string }).text)\n .join('')\n}\n\nfunction toolResultText(blocks: HarnessBlock[]): string {\n return blocks\n .map((block) => (block.type === 'text' ? (block as { text: string }).text : block.type === 'tool-result' ? toolResultText((block as { content: HarnessBlock[] }).content) : ''))\n .join('')\n}\n\nfunction toolsOf(options: HarnessGenerateOptions): PiTool[] | undefined {\n const tools = options.tools?.map((tool) => ({ name: tool.name, description: tool.description, parameters: tool.parameters }))\n return tools && tools.length > 0 ? tools : undefined\n}\n\nfunction assistantApiFor(options: HarnessGenerateOptions, entryEndpoints: readonly string[]): 'openai-completions' | 'openai-responses' {\n void options\n return modelApiForEntry(entryEndpoints)\n}\n\n/**\n * Text-only conversion (no image bytes needed). Images without resolved\n * bytes cannot be sent \u2014 callers must route image-bearing requests through\n * `toPiContextWithImages` instead.\n */\nexport function toPiContext(options: HarnessGenerateOptions, entryEndpoints: readonly string[] = []): PiContext {\n const providerId = options.provider\n const assistantApi = assistantApiFor(options, entryEndpoints)\n const toolNames = new Map<string, string>()\n const messages: PiMessage[] = []\n for (const message of options.messages) {\n if (message.role === 'system') {\n const text = flattenText(message)\n if (text.length > 0) messages.push({ role: 'user', content: text, timestamp: 0 })\n continue\n }\n if (message.role === 'assistant') {\n const assistant = toPiAssistant(message, providerId, assistantApi)\n for (const block of assistant.content) {\n if (block.type === 'toolCall') toolNames.set(block.id, block.name)\n }\n messages.push(assistant)\n continue\n }\n if (message.role === 'developer') continue\n if (message.role === 'tool') {\n const text = flattenText(message)\n messages.push({\n role: 'toolResult',\n toolCallId: message.toolCallId ?? '',\n toolName: message.toolCallId ? (toolNames.get(message.toolCallId) ?? 'unknown') : 'unknown',\n content: [{ type: 'text', text: text || '(no output)' }],\n isError: message.isError ?? false,\n timestamp: 0,\n })\n continue\n }\n for (const block of message.content) {\n if (block.type === 'image') {\n throw new Error('dsh-magpie-connect: image input requires the attachment service (use toPiContextWithImages)')\n }\n if (block.type === 'file') {\n // Files are projected to handle text by the harness; keep a stable\n // placeholder when one arrives unprojected.\n continue\n }\n }\n const text = flattenText(message)\n const fileText = message.content\n .filter((block) => block.type === 'file')\n .map((block) => `[file: ${(block as Extract<HarnessBlock, { type: 'file' }>).attachment.name}]`)\n .join('\\n')\n const combined = [text, fileText].filter((part) => part.length > 0).join('\\n')\n const results = message.content.filter((block) => block.type === 'tool-result') as Array<\n Extract<HarnessBlock, { type: 'tool-result' }>\n >\n if (combined.length > 0 || results.length === 0) {\n messages.push({ role: 'user', content: combined, timestamp: 0 })\n }\n for (const result of results) {\n messages.push({\n role: 'toolResult',\n toolCallId: result.toolCallId,\n toolName: toolNames.get(result.toolCallId) ?? 'unknown',\n content: [{ type: 'text', text: toolResultText(result.content) || '(no output)' }],\n isError: result.isError ?? false,\n timestamp: 0,\n })\n }\n }\n const context: PiContext = { messages }\n if (typeof options.system === 'string' && options.system.length > 0) context.systemPrompt = options.system\n const tools = toolsOf(options)\n if (tools) context.tools = tools\n return context\n}\n\n/**\n * Image-aware conversion: retained images resolve to request bytes via the\n * attachment service; offloaded images become placeholder text.\n */\nexport async function toPiContextWithImages(\n options: HarnessGenerateOptions,\n attachments: AttachmentStore,\n entryEndpoints: readonly string[] = [],\n): Promise<PiContext> {\n const providerId = options.provider\n const assistantApi = assistantApiFor(options, entryEndpoints)\n const requestImages = await prepareRequestImages(options.messages, attachments, options.signal)\n const toolNames = new Map<string, string>()\n const messages: PiMessage[] = []\n for (const message of options.messages) {\n if (message.role === 'system') {\n const text = flattenText(message)\n if (text.length > 0) messages.push({ role: 'user', content: text, timestamp: 0 })\n continue\n }\n if (message.role === 'assistant') {\n const assistant = toPiAssistant(message, providerId, assistantApi)\n for (const block of assistant.content) {\n if (block.type === 'toolCall') toolNames.set(block.id, block.name)\n }\n messages.push(assistant)\n continue\n }\n if (message.role === 'developer') continue\n if (message.role === 'tool') {\n messages.push({\n role: 'toolResult',\n toolCallId: message.toolCallId ?? '',\n toolName: message.toolCallId ? (toolNames.get(message.toolCallId) ?? 'unknown') : 'unknown',\n content: userContentToBlocks(message.content, requestImages),\n isError: message.isError ?? false,\n timestamp: 0,\n })\n continue\n }\n const content = userContentWithImages(message.content, requestImages)\n const results = message.content.filter((block) => block.type === 'tool-result') as Array<\n Extract<HarnessBlock, { type: 'tool-result' }>\n >\n // userContentWithImages already folds nested tool-results to text; emit\n // first-class toolResult messages for pi-ai tool continuity.\n if (typeof content === 'string' ? content.length > 0 || results.length === 0 : true) {\n messages.push({ role: 'user', content, timestamp: 0 })\n } else if (typeof content === 'string' && content.length === 0 && results.length === 0) {\n messages.push({ role: 'user', content, timestamp: 0 })\n }\n for (const result of results) {\n messages.push({\n role: 'toolResult',\n toolCallId: result.toolCallId,\n toolName: toolNames.get(result.toolCallId) ?? 'unknown',\n content: userContentToBlocks(result.content, requestImages),\n isError: result.isError ?? false,\n timestamp: 0,\n })\n }\n }\n const context: PiContext = { messages }\n if (typeof options.system === 'string' && options.system.length > 0) context.systemPrompt = options.system\n const tools = toolsOf(options)\n if (tools) context.tools = tools\n return context\n}\n\nfunction userContentToBlocks(blocks: HarnessBlock[], requestImages: Map<string, RequestImageVersion>): PiContentBlock[] {\n const converted = userContentWithImages(blocks, requestImages)\n if (typeof converted === 'string') return [{ type: 'text', text: converted || '(no output)' }]\n return converted.length > 0 ? converted : [{ type: 'text', text: '(no output)' }]\n}\n", "/**\n * Stall watchdog for upstream event streams.\n *\n * pi-ai surfaces transport failures as `error` events, but a silently\n * stalled SSE connection (headers sent, then no bytes ever again) produces\n * *no* event at all: the consumer's `for await` blocks until the SDK-level\n * overall timeout or the harness timeout. This wrapper races every `next()`\n * against a timer and, on expiry, aborts the upstream (best effort, via\n * `onTimeout`) and yields a synthetic `error` event so the normal chunk\n * translation turns the stall into a fast `TIMEOUT` finish instead of a\n * minutes-long hang.\n *\n * Two phases: time-to-first-event (queueing happens here) and idle-between-\n * events (a healthy stream emits frequently). Either phase accepts a\n * non-positive value to disable its timer (wait forever, the old behavior).\n */\n\nimport type { PiEvent } from './events.ts'\n\nexport interface StallTimeouts {\n /** Max wait for the first upstream event (connection + queueing). */\n firstEventTimeoutMs: number\n /** Max silence between two upstream events once streaming. */\n idleTimeoutMs: number\n}\n\nconst TIMED_OUT: unique symbol = Symbol('watchdog-timed-out')\n\nfunction raceTimeout<T>(promise: Promise<T>, ms: number): Promise<T | typeof TIMED_OUT> {\n let timer!: ReturnType<typeof setTimeout>\n const timeout = new Promise<typeof TIMED_OUT>((resolve) => {\n timer = setTimeout(() => resolve(TIMED_OUT), ms)\n const t = timer as unknown as { unref?: () => void }\n t.unref?.()\n })\n const cleanup = (): void => clearTimeout(timer)\n return Promise.race([promise.then((value) => {\n cleanup()\n return value\n }, (err: unknown) => {\n cleanup()\n throw err\n }), timeout])\n}\n\nfunction stallErrorEvent(model: string, waitMs: number, first: boolean): PiEvent {\n const phase = first ? 'waiting for the first upstream event' : 'waiting for the next upstream event'\n return {\n type: 'error',\n error: {\n api: 'openai-completions',\n provider: 'dsh-magpie-connect',\n model,\n content: [],\n usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 },\n stopReason: 'error',\n // Must read as a timeout (events.ts classifies on /timeout/i -> TIMEOUT).\n errorMessage: `upstream stream timeout: no upstream events for ${Math.round(waitMs / 1000)}s while ${phase} (model \"${model}\") \u2014 the stalled stream was aborted`,\n timestamp: Date.now(),\n },\n } as PiEvent\n}\n\nexport async function* withStallTimeout(\n events: AsyncIterable<PiEvent>,\n timeouts: StallTimeouts,\n options: { model?: string; onTimeout?: () => void } = {},\n): AsyncGenerator<PiEvent> {\n const iterator = events[Symbol.asyncIterator]()\n try {\n let first = true\n for (;;) {\n const waitMs = first ? timeouts.firstEventTimeoutMs : timeouts.idleTimeoutMs\n if (!(waitMs > 0 && Number.isFinite(waitMs))) {\n const next = await iterator.next()\n if (next.done) return\n first = false\n yield next.value\n continue\n }\n const next = await raceTimeout(iterator.next(), waitMs)\n if (next === TIMED_OUT) {\n try {\n options.onTimeout?.()\n } catch {\n // Teardown must never mask the stall itself.\n }\n yield stallErrorEvent(options.model ?? 'unknown', waitMs, first)\n return\n }\n if (next.done) return\n first = false\n yield next.value\n }\n } finally {\n // Best-effort detach without awaiting: the upstream may be the very thing\n // that is stuck, so waiting for its return() could hang teardown too.\n void Promise.resolve(iterator.return?.()).catch(() => {})\n }\n}\n", "/**\n * Plugin configuration (cordis config object, injected via cordis.patch.yml).\n *\n * The adapter registers a DSH LlmAdapter streaming directly from the Magpie\n * LAN gateway (the configured API root): chat completions for most models plus the\n * Responses API for responses-only models. No child process, no proxy/pool.\n * The LAN gateway needs no credential \u2014 `apiKey` defaults to a sentinel that\n * satisfies the pi-ai client without changing the wire.\n *\n * The settings sidebar page can override `baseUrl`/`apiKey` at runtime (saved\n * to `<dataDir>/settings.json`, applied without restart) and hides models\n * from the picker. Page values win per-field; patch config is the fallback.\n */\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nexport interface DshMagpieConnectConfig {\n /**\n * Provider route id registered into DSH (the grouping key in the model\n * picker). Changing it after sessions already reference the old id will\n * orphan those model selections \u2014 prefer `displayName` for a cosmetic\n * rename.\n */\n providerId?: string\n /**\n * Display name shown in the model picker for this provider.\n * Defaults to the explicit `providerId` when one is given,\n * otherwise to `'Magpie'`.\n */\n displayName?: string\n /**\n * Versioned API root, e.g. `http://127.0.0.1:3425/v1`. The version is part of\n * the value, not something this plugin adds: a gateway that later serves\n * `/v2` or `/v3` is reached by changing this one string. Trailing slashes are\n * stripped; the path is kept.\n */\n baseUrl?: string\n /**\n * Gateway credential. Required: an empty key leaves the plugin unconfigured\n * even though `baseUrl` has a default, so the picker stays empty until it is\n * set (both are required by `isConfigured`).\n */\n apiKey?: string\n /**\n * Plugin state directory (status snapshot, settings file, catalog cache).\n * Defaults to `~/.dsh-magpie-connect`.\n */\n dataDir?: string\n /** Model list refresh interval in seconds. */\n refreshSeconds?: number\n /**\n * Connection-setup retries for transient upstream failures (429/5xx with\n * backoff). Turn-level retries stay owned by DSH.\n */\n maxRetries?: number\n /** Overall upstream request cap in milliseconds. */\n timeoutMs?: number\n /**\n * Stall watchdog: max wait for the first upstream event in milliseconds\n * (queueing happens here). Non-positive disables that phase.\n */\n firstEventTimeoutMs?: number\n /**\n * Stall watchdog: max silence between upstream events in milliseconds.\n * Non-positive disables that phase.\n */\n idleTimeoutMs?: number\n}\n\nexport const defaults = {\n providerId: 'dsh-magpie-connect',\n displayName: 'Magpie',\n baseUrl: 'http://127.0.0.1:3425/v1',\n apiKey: '',\n refreshSeconds: 300,\n maxRetries: 2,\n timeoutMs: 300_000,\n firstEventTimeoutMs: 90_000,\n idleTimeoutMs: 60_000,\n} as const\n\nexport type ResolvedConfig = Required<\n Pick<DshMagpieConnectConfig, 'providerId' | 'refreshSeconds'>\n> &\n DshMagpieConnectConfig & { displayName: string; baseUrl: string; apiKey: string; dataDir: string }\n\nexport function resolveConfig(config: DshMagpieConnectConfig = {}): ResolvedConfig {\n const providerId = config.providerId ?? defaults.providerId\n return {\n ...defaults,\n ...config,\n providerId,\n displayName: config.displayName ?? (config.providerId ?? defaults.displayName),\n baseUrl: config.baseUrl ?? defaults.baseUrl,\n apiKey: config.apiKey ?? defaults.apiKey,\n dataDir: config.dataDir ?? join(homedir(), '.dsh-magpie-connect'),\n }\n}\n", "import { readFile, writeFile, mkdir, rename, rm } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Page-owned runtime settings, persisted to a local JSON file.\n *\n * Precedence for the effective endpoint: settings page (this file, only the\n * fields the user actually saved) > cordis patch config > built-in defaults.\n * Hidden models only ever come from this file \u2014 the patch config has no such\n * field, so an empty/absent list means \"show everything\".\n */\n\nexport interface MagpiePageSettings {\n baseUrl?: string\n apiKey?: string\n hiddenModels?: string[]\n}\n\nexport interface EffectiveEndpoint {\n baseUrl: string\n apiKey: string\n}\n\nexport const SETTINGS_FILE = 'settings.json'\n\nexport function defaultSettingsPath(dataDir: string): string {\n return join(dataDir, SETTINGS_FILE)\n}\n\n/**\n * Normalize an API base URL: must be http(s), no trailing slash, path kept.\n *\n * The path is load-bearing, not a mistake to be trimmed: the gateway versions\n * its API (`/v1`, and later `/v2`, `/v3`), so this value *is* the prefix every\n * request is built on. The plugin appends only the resource (`/models`,\n * `/chat/completions`), never a version of its own \u2014 an origin-only value\n * would make a future `/v2` unreachable.\n */\nexport function normalizeBaseUrl(raw: unknown): string {\n if (typeof raw !== 'string' || raw.trim() === '') throw new Error('dsh-magpie-connect: baseUrl must be a non-empty http(s) URL')\n const trimmed = raw.trim()\n let url: URL\n try {\n url = new URL(trimmed)\n } catch {\n throw new Error(`dsh-magpie-connect: baseUrl is not a valid URL: ${trimmed}`)\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new Error(`dsh-magpie-connect: baseUrl must use http(s): ${trimmed}`)\n }\n // Query and fragment are never part of a base URL and would corrupt every\n // appended path; drop them rather than storing a value the wire ignores.\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`\n}\n\n/** Normalize a hidden-model list: strings only, deduped, order kept. */\nexport function normalizeHiddenModels(raw: unknown): string[] {\n if (raw === undefined) return []\n if (!Array.isArray(raw)) throw new Error('dsh-magpie-connect: hiddenModels must be an array of model ids')\n const out: string[] = []\n for (const entry of raw) {\n if (typeof entry !== 'string') throw new Error('dsh-magpie-connect: hiddenModels must be an array of model ids')\n const id = entry.trim()\n if (id !== '' && !out.includes(id)) out.push(id)\n }\n return out\n}\n\n/** Validate + normalize a page payload (full or partial). Unknown keys are dropped. */\nexport function normalizePageSettings(raw: unknown): MagpiePageSettings {\n if (!raw || typeof raw !== 'object') throw new Error('dsh-magpie-connect: settings payload must be an object')\n const record = raw as Record<string, unknown>\n const out: MagpiePageSettings = {}\n if (record.baseUrl !== undefined) out.baseUrl = normalizeBaseUrl(record.baseUrl)\n if (record.apiKey !== undefined) {\n if (typeof record.apiKey !== 'string' || record.apiKey.trim() === '') {\n throw new Error('dsh-magpie-connect: apiKey is required and must not be empty')\n }\n // Surrounding whitespace is always a paste artifact in Bearer keys.\n out.apiKey = record.apiKey.trim()\n }\n if (record.hiddenModels !== undefined) out.hiddenModels = normalizeHiddenModels(record.hiddenModels)\n return out\n}\n\n/**\n * Merge patch config with page settings: page wins per-field, but only for\n * fields it actually saved (absent page fields fall back to patch/defaults).\n */\nexport function resolveEffectiveEndpoint(\n patch: { baseUrl: string; apiKey: string },\n page: MagpiePageSettings,\n): EffectiveEndpoint {\n return {\n baseUrl: page.baseUrl ?? patch.baseUrl,\n apiKey: page.apiKey ?? patch.apiKey,\n }\n}\n\n/** Fully configured only when both URL and key are present. Either missing gates everything. */\nexport function isConfigured(endpoint: EffectiveEndpoint): boolean {\n return endpoint.baseUrl !== '' && endpoint.apiKey !== ''\n}\n\n/** In-memory settings with file persistence. Synchronous reads for the hot path. */\nexport class SettingsStore {\n #path?: string\n #settings: MagpiePageSettings = {}\n #listeners = new Set<() => void>()\n\n constructor(options: { path?: string } = {}) {\n this.#path = options.path\n }\n\n /** Load persisted settings (missing/corrupt file reads as empty, never throws). */\n async load(): Promise<MagpiePageSettings> {\n if (!this.#path) return this.get()\n try {\n const raw = JSON.parse(await readFile(this.#path, 'utf8')) as unknown\n this.#settings = normalizePageSettings(raw)\n } catch {\n this.#settings = {}\n }\n return this.get()\n }\n\n get(): MagpiePageSettings {\n return { ...this.#settings, hiddenModels: [...(this.#settings.hiddenModels ?? [])] }\n }\n\n /** Merge a partial payload, persist, and notify listeners. */\n async save(partial: unknown): Promise<MagpiePageSettings> {\n const normalized = normalizePageSettings(partial)\n this.#settings = { ...this.#settings, ...normalized }\n await this.persist()\n this.#emit()\n return this.get()\n }\n\n /** Replace the whole document (used at startup after load). */\n replace(next: MagpiePageSettings): void {\n this.#settings = { ...next, hiddenModels: [...(next.hiddenModels ?? [])] }\n this.#emit()\n }\n\n onChange(listener: () => void): () => void {\n this.#listeners.add(listener)\n return () => {\n this.#listeners.delete(listener)\n }\n }\n\n async persist(): Promise<void> {\n if (!this.#path) return\n const tmp = `${this.#path}.${process.pid}.tmp`\n await mkdir(dirname(this.#path), { recursive: true })\n await writeFile(tmp, JSON.stringify(this.#settings, null, 2), 'utf8')\n await rm(this.#path, { force: true })\n await rename(tmp, this.#path)\n }\n\n #emit(): void {\n for (const listener of [...this.#listeners]) {\n try {\n listener()\n } catch {\n // listeners must never break the save path\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["join", "writeFile", "name", "name", "text", "next", "join", "readFile", "writeFile", "mkdir", "rename", "rm", "dirname", "join", "join", "writeFile"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-magpie-connect",
|
|
3
3
|
"description": "Magpie LAN gateway models for DeepSeek Harness (DSH): native dsh-llm adapter plugin with image + responses + thinking levels.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|