Dashboard › opencode › Distillation
e719fa20-4f91-4edf-8598-daeb638fb8f9["lore_tm_v1_0w0ficuk3ZAaFPROJd_nvRoKXr1TLaa7lyRfzajMlH4","lore_tm_v1_6fR4z9NEsc2j9VW6mxyFTN6DIW8O1UnXW_iBIcqxyVM","lore_tm_v1_UN38y9umgSO5wvCGCxukdPvsK0-Dx7PySX8Aezersl4"]
customize-opencode guidance states the authoritative OpenCode configuration schema is https://opencode.ai/config.json; every opencode.json should declare "$schema": "https://opencode.ai/config.json", and undocumented or uncertain fields must be checked against the schema rather than guessed because OpenCode hard-fails on invalid configuration.opencode.json, an agent file, a skill, a plugin, or another config-time file, the user must quit and restart OpenCode.model always carries a provider prefix, such as "anthropic/claude-sonnet-4-6"; skills is an object with paths and/or urls, not an array; references is an alias-keyed object; agent and command are name-keyed objects, not arrays; and plugin is an array containing strings or [name, options] tuples, not an object.mcp[name].command is an array of strings, never a single string, and type is required.permission is either a string action or an object keyed by tool name; per-agent permission overrides top-level permission, and Plan Mode is controlled by the plan agent’s permission ruleset with edit: deny *.name is required, lowercase hyphen-separated, limited to 64 characters, and must match the folder name; description is effectively required because skills without one are filtered out and never surfaced to the model.{env:VAR} and {file:path} interpolation, while shell-style ${VAR} is not substituted.$schema and all existing fields not requested for modification, and to prefer creating agent, command, skill, and plugin definitions as new files in their proper locations instead of inlining everything into opencode.json./home/byk/Code/opencode-lore/packages/opencode/src/internal.ts documents that helper functions must remain outside the legacy plugin entry module ./index.ts: OpenCode’s getServerPlugin/getLegacyPlugins treats every exported function as a plugin instance and invokes it. Exporting applyLoreProviderConfig from the entry module previously inserted its undefined return into the hooks array and caused undefined is not an object (evaluating 'A.event'); keeping helpers in internal.ts leaves the entry module exposing only the plugin function.gatewayAccessHeadersForRemote() in /home/byk/Code/opencode-lore/packages/opencode/src/internal.ts injects GATEWAY_AUTH_HEADER only when normalized LORE_REMOTE_URL equals the normalized gateway base and LORE_GATEWAY_AUTH_TOKEN exists.shouldForwardUpstreamExtraHeader() excludes all x-lore-* headers plus x-api-key, x-goog-api-key, and authorization, because provider and Lore credentials are applied by the gateway rather than this hop.applyLoreProviderConfig() pins every configured OpenCode provider’s options.baseURL to ${gatewayBase}/v1, deep-merges provider and options fields to preserve custom headers and model overrides, and creates an openai provider entry when absent.applyLoreProviderConfig() states OpenCode’s resolveSDK() always passes options.baseURL to the @ai-sdk factory; @ai-sdk’s loadOptionalSetting() consults the environment variable only when the factory receives an undefined baseURL. Therefore OPENAI_BASE_URL and ANTHROPIC_BASE_URL are bypassed, while providers including Google, Mistral, Groq, Cohere, xAI, Perplexity, Together AI, Vercel, Alibaba, DeepInfra, Gateway, OpenRouter, and Cerebras have no base-URL environment variable, making iteration over cfg.provider the universal routing lever.applyLoreProviderConfig() explanation states that without provider pinning, OpenCode can derive Anthropic’s base URL from OPENAI_BASE_URL while stripping /v1, causing requests to hit http://host/messages; the Lore gateway routes /v1/messages, and the fetch interceptor skips 127.0.0.1 to avoid loops, so the unpinned request ends in a bare /messages 404.applyLoreProviderConfig() sets headerTimeout: false for provider IDs openai and openai-codex, overriding OpenCode’s Codex 10-second header deadline because Lore may take longer while preparing context and the gateway owns the foreground request deadline.probeGateway() defaults to a 1500 ms timeout, probes ${baseURL}/health, uses Node http/https for loopback URLs, uses fetch otherwise, accepts only HTTP 2xx from the loopback probe, and returns false on failure.surfaceGatewayUnavailable() logs the fatal Lore-unavailable message and uses client?.tui?.showToast({ body: { title: "Lore", message, variant: "error" } }); the toast posts through the OpenCode server and never writes a raw byte to the terminal, avoiding TUI screen corruption caused by the replaced process.stderr.write approach.surfaceGatewayUnavailable() is deliberately fire-and-forget and suppresses both synchronous throws and promise rejection from showToast; a missing/headless TUI or an older server without /tui/show-toast must not crash a degraded-but-running session./home/byk/Code/opencode-lore/packages/core/src/fetch-interceptor.ts defines FetchInterceptorConfig with gatewayBase, per-request getHeaders(): Record<string, string>, and optional onRequestHeaders(headers: Headers)./messages, /chat/completions, or /responses under /v1/, /api/v1/, /api/, /openai/v1/, or /anthropic/v1/, plus ChatGPT Codex /codex/responses; NON_STANDARD_PATH_REWRITES maps "/codex/responses" to "/v1/codex/responses".detectProtocolFromBody() identifies openai-responses from input, max_output_tokens, or previous_response_id; identifies anthropic from top-level system plus a messages array; and otherwise identifies openai from a messages array, requiring a truthy model and returning null for malformed or unrecognized JSON.ArrayBuffer, and typed-array request bodies without consuming them; it intentionally does not inspect ReadableStream bodies or bodies available only through a Request object.anthropic → /v1/messages, openai → /v1/chat/completions, and openai-responses → /v1/responses; recognized endpoint suffixes are ordered longest-first as /chat/completions, /codex/responses, /responses, /messages.Rewrite routing data preserves gatewayUrl, upstreamBase, and the client’s full original upstreamPath; x-lore-upstream-path allows exact forwarding for endpoints that omit /v1, including GitHub Copilot’s /chat/completions from issue #1052, and for providers using non-standard prefixes.shouldIntercept() refuses requests already targeting the gateway, local hosts localhost, 127.0.0.1, 0.0.0.0, ::1, and [::1], and non-LLM paths; it intercepts only recognized remote LLM API paths.Symbol.for("lore.fetchInterceptor.originalFetch"). This shared original-fetch slot is required across multiple bundled copies of @loreai/core; a module-scoped guard would allow stacked interceptors and recreate the gateway-to-interceptor infinite loop associated with issue #1027.buildGatewayHeaders() preserves original request headers, sets x-lore-upstream-url, conditionally sets x-lore-upstream-path when it begins with /, and adds dynamic getHeaders() values only when the header is not already present; failures in getHeaders() and onRequestHeaders() are logged rather than propagated.installFetchInterceptor() has two interception paths: Path 1 rewrites URL-pattern-matched calls; Path 2 parses recognizable JSON bodies for LLM-looking but non-standard paths and routes them to the protocol’s canonical gateway endpoint. Unrecognized LLM-looking paths bypass Lore and emit one warning per ${host}${pathname} via warnedPaths./messages, /completions, or /responses; direct requests already targeting the gateway are not intercepted but their headers are still passed to onRequestHeaders so in-process extensions can scope side-channel requests.preconnect, using globalThis.fetch = Object.assign(interceptor, originalFetch); cleanup restores the original fetch and resets the shared slot to null.