Commit Graph
23 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 85bf0a99de fix: identify OpenCode Go requests by session 2026-09-03 11:49:35 +03:00
Bohdan Triapitsyn 8aba30ac43 revert: stop forwarding the Small Model override into the managed OpenCode config
Reverts #2687. In real use the injected small_model behaves poorly with
OpenCode: its internal small-model consumers and OpenChamber's own small
model are different things and must stay configured separately.
2026-08-29 01:14:02 +03:00
Bohdan Triapitsyn 48bcac1758 fix: harden and de-slop the merged sidebar/chat/settings batch
Post-merge follow-ups for #2740 #2735 #2734 #2690 #2676 #2738 #2684
#2689 #2733 #2739 #2462 #2687 #2736 #2618 #2697, plus three regressions
found while reviewing them:

- ctrl/cmd+digit while typing no longer switches session tabs (#2503 was
  still open in practice: the guard only covered the mod+alt surface binding)
- Shiki template-call sanitizer now covers every bundled grammar, including
  the js/ts aliases and embedding grammars; timed-out highlight requests are
  memoized and no longer cancel unrelated in-flight requests
- settings flush on suspend uses keepalive and also fires on Capacitor
  appStateChange; keeps the selected model persisted across mode switches
- remote-only branches fetch before checkout; range helpers fail clearly
- git status invalidation now fires for runtime adapters too
- settings number inputs and select triggers size in ch so they scale with
  the interface font
- recent-activity timestamps tick from one list-level ticker
- Markdown preview find goes through the shared find_in_file keybind with
  containment, no longer counts its own bar, and debounces observer runs
- #2676 reverted; #2524 fixed by fading the sticky header's own background
  instead of overlaying the content below it
- sticky group headers in the model picker and sidebar render again
  (oc-sticky-fade-scroller class restored after 9b9d7069c)
- project switcher names are left-aligned again (wrapper lost in 26dbc2f30)
- tool card quick-open icon is always visible and opens the same line as the
  expanded card's button
- tautological tests replaced or removed; new oxlint findings fixed
2026-08-29 01:06:43 +03:00
Bohdan Triapitsyn 2a5a25d5a2 fix(small-model): harden configured headers 2026-08-28 23:50:04 +03:00
Dmitrii 3903cb53b1 fix(small-model): send configured provider headers (#3214)
readProviderConfig read only options.baseURL and options.apiKey, and the
OpenAI-compatible dispatch hardcoded a bearer token, so provider
options.headers never reached the request. OpenCode sends those headers on
every chat turn, which left the small model authenticating differently from
the request path against the same URL.

Providers behind a gateway that authenticates on its own header, such as the
Ocp-Apim-Subscription-Key default of Azure API Management, answered 401 for
walkthroughs, session goal audits, titles and commit summaries while the same
model worked in chat.

Read options.headers alongside the API key, resolve {env:...} and {file:...}
in the values with the existing resolveConfigApiKey, and merge them into the
request after the bearer default so a gateway whose header is the credential
can override it.

Closes #3213
2026-08-28 23:46:05 +03:00
Bohdan Triapitsyn ac1881fab7 fix(server): forward Small Model override to managed OpenCode config
The Small Model override chosen in Settings never reached the managed
OpenCode process config, so OpenCode's own title/summary generation
kept using its fallback chain instead of the user's explicit choice
and sessions stayed untitled.

Merge main into this branch to pick up the includeWeb/includeMemory
flags added to prepareManagedOpenCodeEnv, and re-apply the Small
Model injection on top of that current env shape in
getManagedOpenCodeEnv (server/index.js).

Closes #2497
2026-08-28 23:45:04 +03:00
Bohdan Triapitsyn 0596af5761 Merge pull request #3000 from mpeter/fix/anthropic-small-model-base-url
fix(small-model): route anthropic provider through configured baseURL
2026-08-28 01:23:22 +03:00
Bohdan Triapitsyn fb9306745e Merge pull request #2774 from IngTian/fix/omit-thinking-config-non-reasoning
fix(small-model): omit thinkingConfig for non-reasoning Google models
2026-08-27 23:14:53 +03:00
Matt Peter 56b5e9971f fix(small-model): don't double up /v1 in Anthropic baseURL override
Match @ai-sdk/anthropic exactly: baseURL is the full API prefix, so
/messages is appended as-is instead of unconditionally inserting /v1,
which broke configs where the baseURL already ends in /v1 (the common
form OpenCode itself passes to @ai-sdk/anthropic).

Addresses review feedback from @btriapitsyn on PR #3000.
2026-08-25 09:42:28 -04:00
Matt Peter f459206e4a fix(small-model): route anthropic provider through configured baseURL
The Anthropic small-model dispatch (session-assist recap/suggestion,
walkthrough, etc.) hardcoded https://api.anthropic.com/v1/messages,
ignoring provider.anthropic.options.baseURL — the same override every
other provider branch already respects. Any setup routing the anthropic
provider through a local proxy (e.g. Meridian for Claude Max / Vertex)
had the small-model call bypass the proxy and hit the real Anthropic API
with whatever placeholder credential the proxy config uses, failing with
401 invalid x-api-key on every call.
2026-08-25 09:42:28 -04:00
Bohdan Triapitsyn 6a09c63392 feat(small-model): resolve plugin-registered providers from the running OpenCode
Plugin providers are registered from a plugin's `config` hook and credentialed
from its `auth` loader, both inside the running OpenCode process. Nothing about
them reaches `opencode.json` or `auth.json`, so resolution that only reads files
could not see them: selecting such a model failed with "has no known API base
URL" while the same model worked in chat (#2666).

`GET /provider` is where that state is visible. A new `runtime-providers`
module keeps one cached snapshot of it and reports, per provider, the
credential and endpoint OpenCode itself resolved. Credential resolution becomes
config -> runtime -> auth.json, and endpoint resolution config -> openai default
-> runtime -> models.dev catalog.

Providers with a dedicated wire format (Copilot, ChatGPT-plan OpenAI, Anthropic,
Google) are excluded from the runtime credential: for them OpenCode reports an
OAuth access token that their real transport does not accept.

opencode zen is excluded when the user has no zen login. OpenCode then reports
the sentinel `apiKey: "public"` and trims its catalog to free models that run on
its own infrastructure; the sentinel is never read as a credential.

Claude Code stays refused for background actions even when a plugin publishes an
OpenAI-compatible endpoint for it, because that endpoint is a facade over the
Claude Agent SDK and spawns the CLI per request.

No capability probe. Asking `GET /models` does identify a plugin whose protocol
lives in its own `fetch`, but measured across the 166 providers with an `api`
URL in the models.dev catalog it also denies six that work and simply have no
`/models` route. A provider that vanishes from the picker explains nothing,
while one that fails on use says why, so availability stops at credential and
endpoint.

The same list drives the Small Model and Changes Walkthrough pickers.

Validated against a real OpenCode with four plugin providers loaded: offered
providers went from 3 to 7, zen and Claude Code stayed out, and a generation
through a plugin-backed model that previously failed now returns.
2026-08-20 00:47:34 +03:00
Bohdan Triapitsyn d353638f39 fix: integrate Claude CLI provider state 2026-08-15 01:59:26 +03:00
Zeying Tian 0bbbcd6445 fix(small-model): omit thinkingConfig for non-reasoning Google models 2026-08-09 03:10:31 -04:00
Serhii Dziupin 8a85073261 fix(server): forward Small Model override to managed OpenCode config
OpenChamber's Settings → Chat → Small Model override only fed OpenChamber's
own /api/small-model/generate utility service; it never reached the managed
OpenCode server, whose internal title/summary generation reads small_model
from its config. With the override injected into OPENCODE_CONFIG_CONTENT at
managed-process launch, session title generation uses the user's explicit
model instead of falling back (or failing to resolve) — fixing sessions that
stayed untitled even with a Small Model configured.

Only an explicit override (smallModelUseDefault === false with a non-empty
smallModelOverride) is injected; "use default" leaves the config untouched
so OpenCode's own resolution chain stays authoritative. Malformed user config
is left unmodified. External OpenCode servers are unaffected (they are not
launched with this env).

Fixes #2497
2026-08-05 13:46:30 +03:00
Cursor AgentandSerhii Dziupin abb396e080 fix(walkthrough): block unauthenticated providers with a friendly refusal
When the walkthrough small model resolves to a provider with no usable
login, readiness was still ready and generate returned a raw 500 message.
Refuse up front with no-provider-login and surface a blocker instead.

Closes openchamber/openchamber#2607

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-04 11:29:58 +00:00
Bohdan Triapitsyn 1d17cb87b3 feat(walkthrough): write walkthroughs in the reader's language
A guided explanation is only useful in a language the reader reads, so the
panel header gets a language picker alongside the model one, defaulting to
the interface language. Like the model, it is request state rather than a
setting: the language travels with the read and the generation, and the one
a walkthrough was written in is stored with it, so reopening a review
describes what is there instead of what a fresh one would be.

Only prose is translated. Hunk aliases resolve back to hunk ids and
icon/importance are validated against fixed English values, so a translated
one would be dropped by the normalizer — silently losing an anchor or a
style. Identifiers and paths stay as they appear in the code.

The language is part of the cache key, and a read now asks the cache for the
exact request it was given before falling back to the pointer. Without that
the panel answered a request to switch languages with the text it already
had, leaving the other language unused in the cache.

Alongside it:

- The answer budget is derived from the resolved model instead of a flat 24k.
  That number was the same for a 64k-context model and for one that admits to
  384k output tokens, and on the latter it was the only reason generation
  failed: the model spent the whole allowance reasoning and returned nothing.
  It is now min(96k, max(24k, a quarter of the context)) capped by the
  catalog's output limit, decided once so the input reserve and the request
  cannot drift apart.
- A read no longer offers Cancel. It is a few hundred milliseconds of git with
  nothing to cancel, and the button flickered on every model or language
  change. When the panel is showing a fallback, a banner names what is on
  screen versus what was asked for — only once the read has settled.
- The header keeps one 32px control height and drops its labels below 680px
  instead of squeezing them to two letters and an ellipsis.

Docs and module documentation updated in every locale.
2026-08-03 01:27:27 +03:00
Bohdan Triapitsyn 34d0ff7383 feat(walkthrough): guided AI walkthrough for diffs, branches, and PRs (#2572)
A diff is ordered by file path, which is almost never the order in which a
change makes sense. This adds a Walkthrough surface that reorders it: the model
groups related hunks into stops, explains what each group changes about
behavior, and orders the stops so each builds on the last. It explains and
orders; judging code stays with the existing Review action.

Reviews uncommitted work (all, staged, unstaged), a branch against its base, or
a pull request. Generation is always user-initiated — nothing runs on a timer,
on a file change, or as a side effect of opening a panel.

Invariants worth preserving:

- Hunk identity is derived on the server and only there. Ids are content
  hashes, so an anchor that no longer resolves is proof the code it described
  changed, and staleness needs no heuristics. The client matches ids to ids and
  never recomputes them; two implementations would have to agree forever.
- The digest is never truncated. A diff that does not fit the model's context
  is refused with an actionable reason, because a walkthrough written against
  half a diff reads as confident and is wrong.
- Nothing disappears. Lockfiles and other generated output are excluded from
  the model's input by name — never by size — and everything no stop covers is
  listed at the end, so "have I seen all of it" stays answerable.
- Cost is explicit. Results are content-addressed, so returning the working
  tree to an earlier state costs nothing; generation outlives its request, so a
  refresh detaches the client rather than discarding paid-for work, and only an
  explicit cancel stops it.

Supporting changes to shared modules:

- git: expose the existing getRangeDiff as GET /api/git
  listUntrackedPaths and getUntrackedDiffs. The latter resolve the repository
  once for a batch instead of per file, taking a panel
  ~340ms on an 80-file working tree.
- small-model: structured output across four wire forma
  and abort signal, and an onOverflow policy so an oversized prompt fails
  loudly instead of being silently clipped. A provider
  remembered so the prompt-side fallback goes first next time.
- models.dev metadata: surface structured_output as tri
  false blocks a model, a missing field does not, because the catalog omits it
  for roughly half of all models.

Desktop and tablet only: VS Code serves Git through its
these routes, and the mobile shell does not consume the surface registry.

Docs: packages/docs walkthrough page in English and all eight locales.
2026-08-02 16:22:55 +03:00
Jakub Syty ddbb3c1db0 fix(web): honor Copilot model endpoints (#2382)
Route small-model requests through each Copilot model's advertised Messages, Responses, or Chat Completions endpoint.
2026-07-23 22:48:18 +03:00
Bohdan Triapitsyn 00e002413d fix: resolve configured provider api keys from env and files
Supports {env:NAME} and {file:path} apiKey substitutions in provider config.
Keeps resolved credentials and file contents server-side.
Adds coverage for env and file-based credential resolution.
2026-07-15 12:55:30 +03:00
Bohdan Triapitsyn f45bb05b07 fix: set Google thinking config by Gemini model version
Uses thinkingLevel for Gemini 3 Flash models
Keeps older Gemini Flash models on thinkingBudget: 0
Updates docs and tests for the new Google request payload
2026-07-15 12:45:06 +03:00
Bohdan Triapitsyn e48a9397f1 feat: surface session goal evaluation model and add diagnostics
Shows the evaluation provider/model used for the latest successful goal audit in the UI.
Persists evaluation model metadata on session goals and covers it in tests.
Adds diagnostic logging for small-model calls and session-goal audit parsing.
2026-07-15 08:56:10 +03:00
Andrey Meshkov 7e248d4e9b Fix: small model dispatch fails for custom OpenAI-compatible providers (#2134) (#2135)
* fix: add support for custom provider base URLs from config

* feat: read custom provider apiKey from config and use it as primary credential when it exists

* doc: update small-model documentation
2026-07-13 08:33:14 +03:00
Bohdan Triapitsyn 28f0736d69 feat: small-model utility calls on existing OpenCode providers (#2049)
Adds a server-side "small model" capability: direct, cheap LLM calls that
reuse the user's existing OpenCode provider logins — the mechanism OpenCode
uses internally for titles and summaries but does not expose through the
SDK or plugins. Zero new dependencies; plain fetch with per-provider wire
formats, credentials never leave the server.

Core (packages/web/server/lib/small-model):
- Resolution mirrors OpenCode's session scoping: explicit settings override
  → small_model from the OpenCode config → family scan within the session's
  provider → the session's own model. The global provider scan only serves
  callers without a session context, and background callers forbid it
  entirely (restrictToPreferredProvider), so conversation content never
  reaches a provider the user didn't pick — explicit choices excepted.
- Per-provider auth replicating OpenCode's plugin loaders: GitHub Copilot
  (device token as bearer, no exchange), ChatGPT plan via the codex
  Responses API (single-flight OAuth refresh written back to auth.json),
  Anthropic messages, Google generateContent, generic OpenAI-compatible.
- OpenCode's free models (opencode/big-pickle, *-free) are never called
  directly; unauthenticated providers are skipped by design.
- Prompt clamping to the model's catalog context limit; thinking disabled
  where a wire switch exists (Z.AI/GLM, MiniMax-M3, Gemini Flash); robust
  content parsing with a clear error when a thinking model spends its whol
  budget on reasoning.
- Settings → Sessions gains a Small Model group: use-default checkbox plus
  an override picker limited to authenticated providers, persisted with
  web/desktop/VS Code sanitization parity.

Consumers:
- Session assist: a server-side watcher on the global SSE hub generates a
  short recap and one suggested follow-up after a session idles quietly fo
  a minute, stored on session metadata (openchamber.assist). Freshness is
  keyed to the last assistant message id, so new activity invalidates the
  payload everywhere with no extra writes. The chat shows the recap under
  the last message after five quiet minutes and the suggestion as a
  dismissible chip above the composer (tap fills the input, never sends).
  Gated by a new Chat setting (default on) that is a hard generation
  switch. Language is anchored to the conversation itself, with a
  script-mismatch guard against model/backend language hallucination.
- TTS: a third input mode, summarized — long replies are condensed to
  spoken prose before playback on any TTS engine.
- Git: commit-message and PR generation moved off the active chat session
  onto the small model fed with real diffs and the commit list (bodies
  included), with a session-transport fallback for free-model-only setups.
- Notes: Add to notes distills long selections into 1-3 dense sentences
  preserving exact identifiers, with verbatim fallback on failure.

Fixes along the way:
- The global event watcher now starts unconditionally; it was gated behind
  the desktop-notify env, leaving the server-side event hub dead in
  packaged apps.
- OpenCode re-emits message.updated for old user messages after idle; the
  watcher no longer mistakes those for new activity.
- Session metadata merges from a fresh read right before the PATCH, so
  writes made during the generation window (suggestion dismissals, review
  links) are preserved; the assist runtime stops during graceful shutdown.
2026-07-05 23:19:10 +03:00