feat(opencode): local LAN provider discovery + auto-discover models - #27554
androidand wants to merge 350 commits into
Conversation
|
The following comment was made by an LLM, it may be inaccurate: Based on the search results, here are the potentially related PRs: Most Related:
Related by Pattern:
Note: PR #27554 (the current PR) appears as the top result in all searches, which is expected. The most directly related duplicate candidate is #26756, which already implements discovery from |
c368353 to
a56a8fe
Compare
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Please have a look, I think people will appreciate this, especially in combination with the updated llama-swap which adds feature parity (and more) with ollama. |
|
It would be very much appreciated. Thanks! |
|
This PR contains too much irrelevant data. You're adding local skills and theme content, along with debugging data from your local system. |
llama-skein streams model-load "loading theme" flavor as reasoning_content SSE deltas tagged with a top-level `skein_loading: true`. opencode persisted them as reasoning, ballooning the session DB to ~16 GB and filling the disk (SqlError → TUI crash). The Vercel ai-sdk discards unknown top-level fields, so the marker is only visible on the RAW chunk. stripSkeinLoading() is a line-buffered SSE transform on the llama-skein local provider's stream that drops skein_loading events before the ai-sdk — they never enter the reasoning/message/persistence path, so they can never be stored, by construction. Their text is forwarded to the SkeinLoading transient channel for live display (a no-op until a display subscriber is wired — that's the remaining half of the task). Unit-tested incl. an event split across stream chunks. Fixes the persistence half of skein-duey (P1); live themed-display is the follow-up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The turn-level loop-detection streak in prompt.ts resets on every run, so a session that breaks out of a stuck loop, auto-recovers via compaction, and breaks again can cycle forever (build -> compact -> build -> ...). Track consecutive breaks per session across runs in SessionCompaction; after 3 consecutive breaks the session is marked loop-dead and auto-compaction refuses to run, surfacing the terminal error instead of another silent recovery cycle. Manual /compact is unaffected, and any genuine turn clears the dead state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…peated text The loop-detection block in runLoop only ever compared turns when neither had a tool call, so any turn with a tool call unconditionally reset the streak — a model stuck retrying the exact same tool call with the exact same arguments (e.g. re-sending an identical peer message after a failure) was invisible to it, and this is the more expensive failure mode since each repeat fires a real side effect. Extract the comparison into session/loop-detect.ts (pure, tested): a turn with tool calls is compared by exact call signature (tool + sorted-key arguments) against the previous turn's; a turn with no tool calls is compared by bigram text similarity as before. The two are never cross-compared, so switching between tool calls and plain text resets the streak rather than counting as a repeat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…de Code SessionStatus and Permission (the only two signals statusFrom reads besides loop state) are both per-process in-memory state. A session another opencode process is actively driving was therefore indistinguishable from one this process knows to be genuinely idle: both are simply absent from `statuses`. resolvePeers (the `peers` tool's awareness roster) then dropped every real opencode sibling, leaving only the Claude Code peer — which has an actual cross-process registry (peer/claude/registry.ts) — visible, even though send_peer_message could already reach those siblings directly by session id. Fall back to treating a session with unresolved idle status as busy when it was updated within the last 45s (well above realistic turn/tool-call cadence). Scoped to resolvePeers only: resolveMessageTargets already treats idle as its intended target, and send_peer_message reads `status` back out to refuse delivery into a session it believes is genuinely mid-turn, so guessing "busy" there would make delivery to a real cross-process peer unreliable instead of fixing anything. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
task_id is meant to be an opaque id this same tool previously returned,
but a model — especially a weaker one — will sometimes invent its own
human-readable label instead (e.g. "review-changes-1"), believing it is
naming the task rather than resuming one. SessionID.make() throws a raw
schema validation error ("Expected a string starting with 'ses', got
...") for anything not shaped like a real session id, and that throw
happened while constructing sessions.get()'s argument — before the
existing catchCause around sessions.get could apply — so it escaped as a
confusing tool error instead of falling through to "create a fresh task"
like an unresolvable-but-well-formed task_id already does.
Guard with Schema.is(SessionID) first, and reword the parameter
description to make clear it must be reused verbatim from a prior
result, not invented.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…es clickable permission.mode (the auto-approve toggle, currently only reachable via <leader>p or the command palette) was missing the slashName that /auto was explicitly freed up for when the queue/backlog command renamed away from that name — "Auto mode" is upstream's name for permission auto-approve, not the queue driver, but nobody ever finished wiring it. Add slashName: "auto" so it behaves like every other slash-visible command. Also make the footer's agent-name label and "● auto" badge clickable (mouse-up, matching the existing loop/backlog badge's ClickText idiom) instead of plain, non-interactive text: clicking the agent name opens the same picker /agents does, and clicking the auto badge dispatches the same permission.mode command /auto and <leader>p already run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
With only a couple of built-in agents, agent_cycle_reverse (shift+tab) was barely distinguishable from agent_cycle (tab) — cycling backward or forward through 2-3 items lands you in nearly the same place. Free shift+tab from agent cycling (still reachable via /agents or the command palette) and give it to permission_mode (auto-approve toggle) instead, alongside the existing <leader>p. Matches the muscle memory of similar tools' mode-cycling without overloading Tab's meaning with state-dependent behavior. Also add a rotating tip for /auto (permission.mode's new slashName), matching the existing tip for agent.cycle — the toggle was otherwise undocumented anywhere a new user would look. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fork:verify flagged four files as unregistered divergence after today's commits: session/loop-detect.ts and its test are new fork-only files (owned); tips-view.tsx and test/tool/task.test.ts are the first fork edits to previously-untouched upstream files (patched, with a marker unique to each edit). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
model-gallery-ui task 6.1: adds the section shell design.md decision 7
describes without changing existing behavior. SettingsModelsV2 becomes a
container with three sub-tabs:
- Installed: the pre-existing provider-grouped model list, moved verbatim
into models-installed.tsx (SettingsModelsInstalledV2) — zero behavior
change, same search/filter/visibility-toggle logic.
- Discover: real but deliberately narrow (models-discover.tsx) — lists
llama-skein hosts via the already-shipped GET /gallery/hosts endpoint
(model-gallery-ui task 5.7), online/offline status, installed-model count.
Not the full catalog search/filter/candidate-card experience (task 6.2) —
proof the gallery backend is reachable from this panel, nothing more.
- Operations: an honest empty state (models-operations.tsx). Real
operation-list data needs the app-side operations client task 7.2 builds;
no fabricated content in the meantime.
Added settings.models.{installed,discover,operations} tab labels and the
Discover panel's strings to en.ts, plus a new plural key
(settings.models.discover.hostInstalledCount) registered in
context/language.tsx's PluralKey union.
tsgo --noEmit -p packages/app: 0 errors (unchanged from baseline, this
package already typechecked clean). oxlint: 1 pre-existing-pattern warning
(narrowing assertion on the tab-value onChange, same shape as elsewhere in
this file tree), 0 errors.
Not yet verified in a running browser — no visual/E2E check performed this
pass; say so explicitly rather than claiming a look that didn't happen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…er the gallery API
… owner's sidecar; owners publish busy/idle to the registry; sidecars run without Claude Code
…, confirmed install, /downloads shows llama-skein operations; SDK regenerated
… every hypothetical-fit call 404'd live
…irmed install) and Operations (progress, cancel, refresh on success)
…ed-store detection, load/unload, hide vs delete, copy/move between hosts; TUI /manage
…ment; register app gallery files
…lete with shared-store warning, copy/move between hosts
…decar registry — Session.list is project-scoped
…; regenerate llama-skein client
…rce_peer; provenance re-download is the fallback
- graphify-out/ (analysis-tool cache, ~32MB) was never gitignored and leaked into git; removed and ignored. - Three stray debug .log files (openapi-ts-error-*.log, debug-storybook.log) leaked local absolute paths; removed, *.log ignored. - artifacts/glm52-rise-video/ (personal AI-video experiment output, unrelated to opencode-skein) and HANDOFF*.md (personal home-lab GPU/VRAM tuning notes) removed — no product value to a public reader, out of scope for this repo. - .opencode/opencode.jsonc: skein MCP command now resolves via PATH instead of a hardcoded personal home directory. - family.ts: provenance comment now says ~/dev/skein instead of the full personal path. Follow-up not done here: several archived openspec/changes/** planning docs still reference real home-lab hostnames (rocky/z4/m3/m5) tied to real incident narratives. Left alone — needs careful per-doc editing to genericize without garbling the technical content, not a blind find-replace.
…scan scanLlamaSwap() opened a Bonjour browser, waited a fixed 1s window, tore it down, and returned whatever arrived — every /local request paid that latency, and any host that answered a beat late was silently missing from that response with no way to get it back short of another request. Adds startPeerRegistry()/getPeerRegistrySnapshot()/stopPeerRegistry() to local/mdns.ts: one Bonjour browser opened at server startup and kept running for the process lifetime, updated by "up"/"down" events as they happen, with LAN/localhost probing and per-peer model refresh moved to background intervals instead of per-request work. Reading a snapshot is synchronous and never waits on network I/O; a peer that misses one refresh cycle is marked `stale`, not dropped. Wired in via a new peer-registry-node.ts (mirrors LocalProviderSync's node shape) added to the app-runtime layer graph. The /local HTTP handler now reads the snapshot instead of calling scanLlamaSwap. scanLlamaSwap itself is untouched and still available for one-shot CLI use (sync.ts's local-provider-sync job). Verified against the real network (localhost + LAN probes found 5 real peers within one refresh cycle); tsgo --noEmit clean.
Replace real hostnames (rocky, z4, m3, m5, proxmox) with generic role-based labels (gpu-host-1..5) across ~14 archived planning docs, preserving the technical narrative (VRAM sizing, GPU model, incident details) — only the hostname changes.
The README was unmodified upstream content: install instructions pointed at opencode.ai/anomalyco packages, and nothing explained the fork's purpose or its pairing with llama-skein. Add a "Why this fork exists" / "Getting started" section up front with the fork's actual positioning and a working curl install command, and flag that the untouched sections below (brew/npm/nix/etc.) install upstream opencode, not this fork.
A peer review found the earlier cleanup was incomplete: /Users/andreas still appeared in 7 files, m5-llamaswap in 1. Fixes: - ECOSYSTEM.md: ~/dev/... instead of the full personal path. - Register packages/opencode/src/local/peer-registry-node.ts in fork/manifest.json (bun run script/fork-verify.ts was failing on it as an unregistered divergence). - 4 archived openspec docs: /Users/andreas/ -> ~/. - Test fixtures (presence-claude, peers, sync): personal paths and a real hostname (m5-llamaswap) swapped for generic equivalents. Not done here, flagged separately: several 192.168.1.x IPs remain in tracked test fixtures, at least one of which (192.168.1.126) matches a real, currently-live host on the owner's network, not a coincidental example address. Left alone pending a decision on scope, same as the larger history-rewrite question this repo now also has (see report).
…nsistent The earlier scrub renamed one m5 occurrence to gpu-host-1 but not the rest of this file's rocky/m3/m5 slugs, and canonicalName strips the -llamaswap suffix, so the input/assertion pair no longer matched (caught by a peer review before this shipped further). Reverted the premature partial rename here and kept the file using the real hostnames consistently — the upcoming history rewrite's replace-text pass handles renaming all occurrences everywhere in one atomic step, which is the right place to do it, not piecemeal per-file edits. Anonymized this file's IPs to the 192.0.2.0/24 documentation range (RFC 5737) while here.
Issue for this PR
Closes #6231
Closes #27553
Type of change
What does this PR do?
Adds
Local (LAN)discovery in/connectfor local OpenAI-compatible servers.Auto-discovers models for OpenAI-compatible providers from
/models, including limits like context window and max output tokens when available.Compatible with and preserves manually configured/edited providers and models and respects
discoverModels: true|false.Adds context
current / context_lengthwhen available (see llama-swap PR below) to Context in sidebar and footer.Fixes unrelated bugs introduced by rebase, --agent parameter would crash opencode.
Related
Server-side companion: mostlygeek/llama-swap#755 adds
context_lengthandmax_output_tokensto the/v1/modelsresponse. When those fields are present, this PR reads them to configure each model's context window and output token limits automatically — no manual config needed.How did you verify your code works?
bun run typecheckbun test test/provider/provider.test.ts --timeout 30000bun turbo typecheck/connect -> Local (LAN)against multiple local OpenAI-compatible servers on localhost and LAN.Screenshots / recordings
Provider config example
Checklist