Commit Graph
17 Commits
Author SHA1 Message Date
Felix-Ayush 58daea7a48 feat(providers): add Fireworks AI provider preset (#772)
* feat(providers): add Fireworks AI provider preset

Add an OpenAI-compatible Fireworks catalog entry with curated coding
models, models.dev alias mapping, onboarding tests, and README setup.

* fix(provider): use correct Fireworks DeepSeek V3 model ID

Update default and catalog model IDs to deepseek-v3-0324 per Fireworks API.
2026-07-28 22:40:41 +08:00
euxaristiaandClaude Sonnet 5 97ef90d27a chore: remove code unreachable on every supported platform (#706)
* chore: remove code unreachable on every supported platform

The Security and code health job's advisory deadcode step reports 152
unreachable functions on main. Running the same check under GOOS=linux,
windows, and darwin shows 13 of those are false positives whose callers
live in windows build-tagged files, so only functions unreachable on all
three platforms are touched.

Functions with no references anywhere are deleted, including the
internal/reasoning package, which nothing imports. Functions that only
tests reference move into per-package export_test.go files so tests keep
their seams while the production binary sheds the code. Test call sites
of deleted convenience wrappers are ported to the live APIs (scoped tool
constructors, tools.DeferredSource, FinishReason checks).

Kept despite the report: cron.Store.Runs (only reader of runs.jsonl,
which production writes), and agenteval.TraceEventKeys and
MissingTraceEvents (used across packages by the trace contract test).

* docs(config): correct ValidateFile's errors.As claim

Message is a flattened string (.Error() already called on the parse
error), not a wrapped error, so callers can't recover the concrete
json error type via errors.As. Fix the comment to match.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

* chore(trace,skills): drop obsolete Sink adapters and fix stale doc references

The dead-code pass removed the Emit methods but left Sink, NDJSONSink, and
TextSink exported with a contract nothing implements; remove the types with
their contract. Also remove the stray blank line at EOF of trace.go flagged
by gofmt, and update skills package docs and HOW_ZERO_WORKS.md to stop
directing callers to DiscoveryRoots/Duplicates, which are now test seams.

Co-Authored-By: Claude Fable 5 <[email protected]>

* chore: clean up remaining review nits on dead code removal

Replace a pointless fmt.Errorf(...%w...).Error() with fmt.Sprintf
using %v, since the wrapped error was discarded immediately anyway.
Remove a comment block that was duplicated verbatim.

* docs(skills): drop remaining references to test-only List/Get

Load and MergedSkills comments still named List/Get as production
entry points after they moved to export_test.go. Point callers at
Load and ListFromRoots instead.

* chore: remove now-unreferenced export_test.go seams

execSessionManager.len() and likelySandboxDenied/sandboxDenialKind had
no remaining callers after the sandbox unify-command-execution rebase;
the production code they wrapped for tests no longer exists.

---------

Co-authored-by: Claude Sonnet 5 <[email protected]>
2026-07-24 09:04:41 +08:00
nb213andbinyangzhu000-sudo c00a4734b5 Add Atlas Cloud provider preset (#784)
Co-authored-by: binyangzhu000-sudo <[email protected]>
2026-07-22 08:20:21 +08:00
6b9c2f0c08 feat(aimlapi): AI/ML API provider with guided onboarding (top-up + key issuance) (#655)
* feat(aimlapi): AI/ML API guided onboarding (top-up + key issuance)

Add the AI/ML API provider integration to the zero CLI: a guided TUI
onboarding sub-flow that takes an existing key or an email top-up and
writes the issued key into the provider profile, attributed to the
Gitlawb rebate partner.

- internal/aimlapi: OpenAI-compatible client, partner-checkout top-up,
  key validation, streaming, and endpoint config (Paths A/B/C).
- providercatalog / providermodelcatalog: register the aimlapi provider
  with default headers and models.
- tui: aimlapi onboarding screen wired into the provider wizard and
  onboarding flow.

Default endpoints point at production (auth/app/api/pay/aimlapi.com);
each is overridable via AIMLAPI_*_URL env vars.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(aimlapi): address review findings in onboarding flow

- topup: use errors.As for the 5xx retry check so a wrapped APIError
  still retries instead of aborting a paid checkout.
- onboarding: a Path-A top-up on an email with no account now lands on a
  single coherent "key saved, balance not topped up" screen instead of a
  green "ready" plus a red error.
- onboarding: an empty amount field is rejected instead of silently
  falling through to the $25 default.
- collapse the two identical top-up prompt constants into MsgTopUpPrompt
  and drop the dead amountPrompt() branch.
- reuse stdlib maps.Clone instead of a third bespoke header-copy helper.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(aimlapi): retry transient poll errors and sharpen amount validation

* fix(aimlapi): retry transient poll errors and surface real amount errors

* docs(aimlapi): document exported client API; drop out-of-scope gitignore entry

Addresses the two CodeRabbit PR checks on #655:

- Add doc comments to the exported aimlapi client API (Client, APIError,
  the session/checkout/onboarding types and their methods) and
  ParseAmountUSD, to satisfy the docstring-coverage check.
- Remove the unrelated /zero.exe~ .gitignore entry that an earlier
  review-fix commit added; it is out of scope for the provider work, so
  the PR no longer touches .gitignore.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

* fix(aimlapi): render aimlapi row like other recommended providers

* fix(aimlapi): resolve onboarding review findings (Path A funding, base URL, request cancellation)

* fix(aimlapi): validate pasted API keys

* test(aimlapi): clarify pasted key validation

* test(aimlapi): cover key-balance low-balance transition

* fix(aimlapi): reject non-finite top-up amounts

* fix(cli): scope and canonicalize provider headers

* fix(tui): distinguish invalid verification codes

* feat(aimlapi): resume account-bound top-ups safely

* feat(tui): offer top-up for existing AIMLAPI keys

* feat(tui): reuse AIMLAPI env key on first run

* feat(tui): reuse configured AIMLAPI profiles

* test(aimlapi): trim redundant flow coverage

* fix(tui): preserve existing AIMLAPI profile metadata

* fix(tui): clone AIMLAPI headers in env fallback

* fix(aimlapi): address remaining review findings

* fix(aimlapi): harden onboarding lifecycle

* fix(aimlapi): preserve validated checkout context

* fix(aimlapi): harden checkout retry state

* fix(tui): clarify AIMLAPI configured choices

* fix(aimlapi): make checkout retries idempotent

* fix(aimlapi): validate checkout responses

* fix(providers): reconcile aimlapi catalog after rebase

---------

Co-authored-by: Lookoff123 <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
2026-07-17 12:49:38 +08:00
d66a9dda69 feat(providers): add AI/ML API preset (rebased onto main) (#621)
* feat(providers): add AI/ML API preset

* fix(providers): relocate aimlapi preset and drop referral header

Addresses the two review asks on #402:

- Move the AI/ML API descriptor out of catalog slot #2 (it sat above the
  first-party OpenAI/Anthropic/Google entries) down next to openrouter in
  the aggregator cluster. Re-sync expectedCatalogIDs and the
  TransportOpenAICompat order-list to match.
- Drop the always-on X-AIMLAPI-Partner-ID / Integration-Repo /
  Integration-Version attribution headers. There's no partner arrangement
  behind the preset, so a non-overridable referral tag on every user's
  request isn't something to ship on a community preset. aimlapi now rides
  through plain CopyHeaders like every other provider. Removes
  HeadersForCatalog (it existed only for this special-case), reverts the
  factory and discovery call sites to CopyHeaders, and drops the header
  test. The factory/discovery tests now assert the endpoint, auth, and the
  user's own custom headers survive while no attribution header is sent.

* fix(providers): address CodeRabbit review on #621

- Document AIMLAPIID for docstring coverage.
- Apply providerio.CopyHeaders for Anthropic and Google provider paths,
  matching the OpenAI path introduced for aimlapi header isolation.

Co-authored-by: Cursor <[email protected]>

* fix(providers): gofmt factory.go to unblock CI smoke

Remove mixed tab/space indentation in the openai.New options block so
ubuntu-latest fmt-check passes.

---------

Co-authored-by: Dmitry Tumanov <[email protected]>
Co-authored-by: Vasanthdev2004 <[email protected]>
Co-authored-by: Cursor <[email protected]>
Co-authored-by: KRATOS <[email protected]>
2026-07-16 22:57:35 +08:00
ba99fa8d48 fix(provider): stop dropping custom no-auth providers on restart (#558)
* fix(provider): stop dropping custom no-auth providers on restart

Custom OpenAI/Anthropic-compatible catalog entries hardcode
RequiresAuth as a template default for the setup wizard, not a fact
about the user's actual endpoint. Leaving the credential step blank
(e.g. a local llama.cpp server) still stamped the saved profile with
a guessed OPENAI_API_KEY/ANTHROPIC_API_KEY env var, and that same
static RequiresAuth flag then made the profile look like it was
missing a required credential on the next run, silently filtering it
out of /model.

Add a Custom flag to the catalog descriptor so both the wizard
(write side) and setupMissingCredentialEnv (read side) judge these
profiles by their own explicit APIKeyEnv instead of the catalog's
static default.

Fixes #555

Co-Authored-By: Claude Sonnet 5 <[email protected]>

Co-authored-by: Pierre Bruno <[email protected]>

* fix(provider): self-heal legacy custom profiles stamped by the old wizard bug

CodeRabbit flagged that profiles saved before this fix could already
carry the phantom APIKeyEnv the old wizard stamped on every custom
provider (e.g. OPENAI_API_KEY), and the new custom-branch logic would
keep treating any non-empty APIKeyEnv as an explicit requirement —
leaving already-affected users' providers filtered out of /model even
after upgrading.

Treat an APIKeyEnv that exactly matches the catalog's own guessed
default as indistinguishable from unset, so already-broken saved
profiles self-heal without a config.json migration. A deliberately
different env var name (e.g. via --api-key-env) still means auth is
required.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

Co-authored-by: Pierre Bruno <[email protected]>

* fix(provider): centralize custom-endpoint credential logic, fix two more surfaces

Copilot's review of the earlier fix found the same RequiresAuth-as-
authoritative bug in two more places that decide whether a saved
provider needs a credential:

- validateProviderRuntimeReady (zero providers check) would hard-error
  a no-auth custom endpoint with "requires API key".
- providerWizardCredentialLabel and providerCredentialRequired (/providers
  status, wizard summary) would still claim/warn that one was needed.

Added config.ProviderProfile.MissingCredentialEnv as the single source
of truth for this decision (custom-endpoint self-heal included), and
had setupMissingCredentialEnv and validateProviderRuntimeReady delegate
to it. providerWizardCredentialLabel and providerCredentialRequired
take the same Custom-aware fix directly, since their non-catalog
fallback path uses runtime-resolved provider-kind metadata that
shouldn't be collapsed into the canonical helper.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

Co-authored-by: Pierre Bruno <[email protected]>

* fix(ci): request pinned Go toolchain directly

Co-authored-by: Pierre Bruno <[email protected]>

---------

Co-authored-by: Amp <[email protected]>
2026-07-08 08:54:39 +08:00
Eli Lap 1805a8ce09 Introduce opencode-go-anthropic that uses anthropic SDK for qwen and … (#544)
* Introduce opencode-go-anthropic that uses anthropic SDK for qwen and minimax models.

* Adjust naming to match convention format.

* Adjut naming to match convention.

* Update name for opencode-go-anthropic-compatible

* Update curated models for opencode anthropic and adjust test.

* Adjust filter for opencode anthropic provider and cover with tests.

* Adjust discovery model filtering - tolerate ModlIDAllowedForProvider rules.

* Change model descriptors to lowercase for minimax models.

* CLI call triggers model filtering for opencode-go-anthropic-compatible.

* feat: gate runtime model resolution on provider allowlist (PR #544 P2)

Apply ModelIDAllowedForProvider(profile.CatalogID, model) at profile
resolution and ResolveRuntimeMetadata so TUI /model and headless
--model overrides cannot escape the provider-scoped filter for
catalog-backed profiles such as opencode-go-anthropic-compatible.

resolveProfile is the single funnel for both New() and
ResolveRuntimeMetadata, so a single validation call in each return
path covers every caller (TUI command_center, exec, provider health,
context report, zerocommands). Restricted providers reject; the
default branch keeps unrestricted providers unchanged.
2026-07-07 22:43:44 +08:00
Kevin Codex 6247ee8f9c Add Tencent HY3 to Zero OpenGateway catalog (#577) 2026-07-07 17:52:39 +08:00
Jochen aaad4d2712 feat(providers): split minimax zai into intl cn (#398)
* feat(providers): split minimax zai into intl cn

- Add minimaxi-cn (api.minimaxi.com) sharing model catalog with minimax
- Add zai-cn (open.bigmodel.cn) splitting zai from china to intl
- Decouple env vars: ZAI_API_KEY vs ZHIPU_API_KEY for zai/zai-cn,
  MINIMAX_API_KEY vs MINIMAXI_API_KEY for minimax/minimaxi-cn
- Map zai-cn and minimaxi-cn to models.dev providers "zai" and "minimax"
  so the model catalog hydrates for both endpoints

* chore(providers): address review feedback on intl/cn split

- Run gofmt on internal/providermodelcatalog/catalog.go (map-value
  alignment on the \"zai\" entry was off by one space, fails CI lint)
- Update stale comment in TestResolveAppliesZaiInternationalCatalogDefaults
  to say glm-4.5 default (live discovery upgrades to glm-5.2)
- Add missing ProviderKind, APIKeyEnv, and APIKey assertions to
  TestResolveAppliesZaiInternationalCatalogDefaults so it would catch
  a silent regression in ZAI_API_KEY routing
2026-07-03 16:33:11 +08:00
KRATOS b4275e3504 feat(providers): add Meituan LongCat catalog preset (#424)
Add LongCat-2.0 as a first-class OpenAI-compatible provider preset so it can be
selected by name in the setup wizard / `zero providers setup longcat` instead of
hand-entering a custom endpoint.

LongCat-2.0 is Meituan's open, OpenAI- and Anthropic-compatible model. This
wires the OpenAI-compatible transport:
  base URL: https://api.longcat.chat/openai  (Zero appends /chat/completions)
  model:    LongCat-2.0
  auth:     Bearer via LONGCAT_API_KEY

Purely additive — one descriptor plus its catalog/onboarding tests and a README
env-var line; no shared plumbing is touched, so existing providers are
unaffected. Placed with the other OpenAI-compatible third parties (after
moonshot), below the first-party providers.

Verified: go build / vet / test green (catalog + cli onboarding), gofmt clean,
and `zero providers catalog` lists the descriptor with the correct fields.
Endpoint is wired by-spec from LongCat's published docs; a live completion still
needs a real LONGCAT_API_KEY to confirm.
2026-07-03 12:44:11 +08:00
Ashwinhegde19andVasanthdev2004 b1ccb6d9c1 feat(providers): add KiloCode and OpenCode provider support (#388)
* feat(providers): add KiloCode and OpenCode provider support

Add KiloCode (Kilo Gateway), OpenCode Zen, and OpenCode Go as catalog
entries using the existing openAICompat transport. No new runtime code
needed — both gateways expose standard OpenAI-compatible /chat/completions.

Fixes #383

* fix(providers): swap OpenCode Zen default model to deepseek-v4-flash

gpt-5.5 is served via /responses on Zen, not /chat/completions.
deepseek-v4-flash works with openAICompat transport.

Co-Authored-By: Vasanthdev2004
2026-07-02 22:10:54 +08:00
072f3877b5 fix(opengateway): correct gateway host, make it the recommended default (#341)
OpenGateway pointed at gateway.gitlawb.com, which does not resolve
(NXDOMAIN), so every request failed with "no such host". The live gateway
is opengateway.gitlawb.com — a flat OpenAI-compatible endpoint
(/v1/chat/completions, Authorization: Bearer ogw_live_…) that smart-routes
by model id across its upstreams (xiaomi-mimo, minimax, qwen, google,
nvidia, z-ai).

- catalog: fix base URL to https://opengateway.gitlawb.com/v1, default model
  to mimo-v2.5-pro; add a Recommended descriptor field and hoist OpenGateway
  to the top of the catalog so it is the first/badged pick everywhere.
- models: replace the curated model list with the gateway's real upstream
  models; fix the stale model-discovery fallback host.
- providers catalog: sort recommended-first (CLI + snapshot) and render the
  ★ … (recommended) badge; same badge in the TUI /provider wizard and the
  setup onboarding list.
- providerhealth: OpenGateway's flat /v1/models 404s by design, so probe its
  public /health endpoint for connectivity instead of reporting a false fail.
- tests: update catalog order golden + transport/discovery expectations; add
  coverage for the recommended provider and the health-endpoint override.

Co-authored-by: Kevin Codex <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-30 10:14:01 +08:00
Anandan a2e0f3c1fe fix: update ChatGPT Codex OAuth request handling (#299) 2026-06-22 12:56:09 +05:30
Vasanth T ab3d09d78e feat(oauth): Hugging Face + ChatGPT Codex as first-class built-in OAuth presets (#245)
* feat(oauth): add Hugging Face + ChatGPT Codex as first-class built-in OAuth providers

Adds two new built-in OAuth presets that round out the existing OpenRouter and
xAI surfaces, so the TUI `/provider` OAuth chooser and `zero auth login` work
out of the box for these providers.

Hugging Face
- Preset scopes, endpoints, and OIDC issuer are pre-filled; the operator
  supplies a "public" OAuth client_id (no secret) via
  `ZERO_OAUTH_HUGGINGFACE_CLIENT_ID` after registering an app at
  huggingface.co/settings/applications/new.
- Default flow is RFC 8628 device code, so headless/SSH is the first-class
  path (matches the existing xAI posture).
- The bearer works against the OpenAI-compatible Inference Providers router
  at `https://router.huggingface.co/v1` for hundreds of OSS models.

ChatGPT (Codex backend)
- Uses the publicly-shipped Codex CLI client identity
  (`app_EMoamEEZ73f0CkXaXp7hrann`), opt-in via `ZERO_OAUTH_ALLOW_PRESETS=1`,
  overridable via `ZERO_OAUTH_CHATGPT_*`.
- The bespoke `provideroauth.ChatGPTLogin` extracts the `chatgpt_account_id`
  claim from the OIDC ID token (added as `IDToken` on the `oauth.Token`
  struct) and stores it on `Token.Account` so the Codex provider can inject
  it as a header on every request.
- A new `internal/providers/openai/codex.go` wraps the openai provider with
  the Codex-flavored request shape: `originator: codex_cli_rs`,
  `chatgpt-account-id: <claim>`, and a branded User-Agent. The factory
  branches off the catalog id (not the provider kind) so other
  openai-compatible providers keep the standard openai path unchanged.
- The openai provider gained a `SetRequestExtra` callback so the Codex
  wrapper can inject per-request headers without coupling the openai
  provider to Codex.

CLI / TUI
- `zero auth chatgpt` is a one-shot login command (analog to
  `zero auth openrouter`); the TUI's OAuth chooser now lists both new
  providers automatically (the list is built from
  `providercatalog.OAuthProviders()`).
- The provider-wizard visible-row cap was bumped from 8 to 10 so the
  common providers (OpenAI, Anthropic, Google, Ollama, OpenRouter, the
  new Hugging Face, the new ChatGPT, Groq, …) all fit without scrolling
  the test fixtures.

Tests
- `internal/oauth/presets_test.go`: presets resolve correctly with the
  expected endpoints, scopes, and flows; env overrides win; Hugging Face
  preset stays inert without a client_id (the safety posture the existing
  xAI tests assert).
- `internal/providercatalog/oauth_test.go` + `catalog_test.go`: catalog
  now exposes 4 OAuth providers (`openrouter`, `huggingface`, `chatgpt`,
  `xai`); the `chatgpt` descriptor is `OAuth=true` with
  `OAuthDeviceFlow=false` (Codex is loopback-only) and `RequiresAuth=true`
  via an inline IIFE that sets it after the openAICompat helper.
- `internal/provideroauth/chatgpt_test.go`: ID-token claim extraction
  round-trips (happy path, missing claim, missing token, tampered JWS,
  non-string claim); the full login flow runs against a stub auth server
  and verifies the preset's client_id reaches the token endpoint.
- `internal/providers/openai/codex_test.go`: every request gets the three
  Codex headers; the 401-refresh retry path keeps them on the second
  attempt; configured baseURL wins; default originator is `codex_cli_rs`;
  branded User-Agent wins over the openai default; AccountResolver is
  consulted when AccountID is empty; the header is omitted when the
  resolver says no.

Out of scope (separate PRs): multi-account / profiles, generic
template-driven OAuth wizard, MCP / provider OAuth store unification,
subscription-proxy autoconfig.

* fix(ci): gofmt chatgpt_test.go and provider_wizard.go

The ubuntu smoke job's "Check formatting" step lists these two files as
unformatted (struct field alignment). The local gofmt run on Windows
also lists ~170 other files, but those are CRLF-vs-LF noise from
checkout and not real gofmt issues on Linux; the CI only flags the
two struct-alignment changes below.

- internal/provideroauth/chatgpt_test.go: align chatgptTestServer fields
- internal/tui/provider_wizard.go: align ChatGPTOptions literal fields

* fix(codex): hit the /responses path and resolve account id per request

Addresses the two findings in anandh8x's CHANGES_REQUESTED review on #245.

The Codex provider previously routed the chat-completions body to
{baseURL}/chat/completions, which is not the path the Codex backend serves
(chatgpt.com/backend-api/codex/responses, the Responses API). Add an
Endpoint field to openai.Options so a flavor can target a sibling path on
the same host; the Codex constructor sets it to {baseURL}/responses and
falls back to the openai transport unchanged for everything else.

The factory was also passing the OAuth token's Account field as a static
AccountID at construction time. A refresh that rotates the token (and
its account claim) would leave a stale value on the wire. Drop the
static AccountID from the factory wiring; the per-request
AccountResolver (which reads the live store) is now the sole source, so
a refresh takes effect on the next outgoing request.

Tests:
- codex_test.go: handler mounted on /responses; expected path updated;
  TestCodexProviderResolverIsConsultedOnEveryRequest pins the per-request
  resolver behavior (a refresh-style sequence of calls picks up the new
  account id on the second attempt).
- factory_test.go: expected request path updated to /responses.

* Addresses the Codex path shape on /responses.

The previous fix (2381197) corrected the URL and the per-request account
resolver, but the Codex provider still spoke chat-completions: it sent
{model, messages, tools, stream, stream_options, max_completion_tokens}
to /responses and parsed data: [DONE]-terminated SSE frames
(choices[].delta). The Codex backend at chatgpt.com/backend-api/codex
serves the Responses API — input items, function_call /
function_call_output, response.output_text.delta /
response.function_call_arguments.delta events — and rejects a
chat-completions body.

The Codex provider now has its own request/stream path that matches the
Responses schema:

- internal/providers/openai/codex_responses.go (new) holds the wire
  types (responsesRequest, inputItem, contentItem, responsesTool,
  responsesEvent, itemPayload, responsePayload, usagePayload), the
  request builder (system → message/role=system, user/assistant →
  message with text or content parts, prior tool calls → function_call,
  tool results → function_call_output, tools → responsesTool), the
  Responses SSE dispatcher, the tool-call accumulator
  (response.output_item.added registers the call id + name, the
  argument deltas accumulate, response.output_item.done finalizes),
  and the terminal-event handler (response.completed emits usage +
  done, response.failed surfaces the error after the usage chunk so
  token accounting is preserved).

- internal/providers/openai/codex.go: StreamCompletion now marshals a
  Responses request and dispatches via the new streamResponses
  function. The wrapped openai Provider is still constructed for its
  validated endpoint / auth / retry / idle-timeout config and its
  SetRequestExtra callback, but the openai chat-completions stream
  parser is no longer used for Codex. injectCodexHeaders still runs
  on every attempt (including the 401-refresh retry) so the Codex
  headers stay attached to the rotated bearer.

The shared providerio plumbing (auth + 401-retry, SSE scanner with
the idle watchdog, error humanization, secret redaction) is reused
verbatim so the OAuth 401-refresh and bearer-rotation paths stay
identical to the rest of the openai-compatible surface. The
stream-end fallback surfaces a StreamEventDone with
FinishReasonLength when the Codex backend closes the stream without
a response.completed (e.g. an internal truncation), so the runtime
can react instead of hanging on a phantom completion.

Tests:
- internal/providers/openai/codex_test.go:
  - newCodexTestServer now returns a minimal Responses sequence
    (response.created + response.completed); newCodexResponsesServer
    takes a custom payload sequence for richer cases.
  - The two tests with inline handlers (TestCodexProviderResolverIs-
    ConsultedOnEveryRequest, TestCodexProviderRetriesHeadersAfter401)
    use a response.completed payload on the second attempt instead of
    data: [DONE].
  - TestCodexProviderSendsResponsesRequestShape replaces the old
    TestCodexProviderSendsRequestBody and pins the Responses shape:
    the body MUST carry an `input` array of message / function_call
    / function_call_output items and `tools` of function type, and
    MUST NOT carry the chat-completions `messages` field.
  - TestCodexProviderParsesResponsesTextDeltas: response.output_text.
    delta events surface as StreamEventText in order, the
    response.completed usage chunk surfaces as StreamEventUsage
    (with input_tokens, output_tokens, cached_input_tokens, and
    reasoning_tokens from the optional details blocks), and
    StreamEventDone follows.
  - TestCodexProviderParsesResponsesToolCalls:
    response.output_item.added emits StreamEventToolCallStart with
    the right name, response.function_call_arguments.delta events
    emit StreamEventToolCallDelta in order, response.output_item.done
    emits StreamEventToolCallEnd.
  - TestCodexProviderSendsAssistantToolCallsAsInputItems: a runtime
    message with content + tool_calls serializes as BOTH the
    assistant message and a function_call item; a tool result
    serializes as a function_call_output. The expected count is 5
    items (user + assistant text + function_call + function_call_
    output + user) — earlier this test had 4, which would have
    missed the assistant message before the function_call.
  - TestCodexProviderEmitsErrorOnResponseErrorEvent: a response.error
    event surfaces as a single StreamEventError with the server's
    message and stops the scan.
  - TestCodexProviderEmitsErrorOnMalformedStream: a payload that
    parses as JSON but lacks the `type` field is reported as a
    stream error rather than silently dropped.
  - TestCodexProviderEmitsLengthFinishWhenStreamEndsWithout-
    Completion: a stream that closes without a response.completed
    surfaces a StreamEventDone with FinishReasonLength.

Verification:
- `go build ./...` clean (with the user-owned untracked files moved
  aside per the project's working-tree convention).
- `go vet ./internal/providers/...` clean.
- `go test ./internal/providers/... ./internal/providercatalog/...
  ./internal/provideroauth/... ./internal/oauth/...` PASS.
- `gofmt -l` on the three changed files clean.
2026-06-19 07:23:53 +05:30
KRATOS a0b9876b69 OAuth / account login for providers (OpenRouter, xAI) + subscription-proxy support (#217)
* providers: authenticate model calls with an OAuth login when present

Foundation for account/subscription login: when a user is logged in via
`zero auth login <name>`, the OpenAI and Anthropic providers now authenticate
model requests with that OAuth bearer token, falling back to the API key when
there is no login. Auto-select, per the agreed design — no config needed.

  - providerio.SendWithAuthRetry: issues a request with an OAuth bearer (from a
    TokenResolver) or the API key, and retries ONCE with a force-refreshed token
    after a 401. A 401 means the server rejected the request without processing a
    completion, so replay-after-refresh is safe — the same no-duplicate-work basis
    SendWithRetry uses for 429/503. Never sends API key and bearer together.
  - openai + anthropic providers take an optional OAuthResolver and use
    SendWithAuthRetry. When OAuth is active the credential goes on Authorization
    (so Anthropic's x-api-key is correctly omitted). Gemini is unchanged.
  - providers.New threads Options.OAuthResolver to those two providers.
  - cli.oauthResolverForProfile builds the resolver from the unified oauth store:
    it attaches a resolver ONLY when a login already exists for the provider
    name, so API-key users incur no per-request store lookups. The resolver uses
    Manager.GetFresh (refresh near expiry) and Manager.Handle401 (force refresh on
    the 401 retry).
  - oauth: added ErrNoToken sentinel so "not logged in" is distinguishable from a
    real refresh failure (login removed mid-session → clean API-key fallback).

Tests (httptest): API-key fallback (nil resolver / ok=false), bearer wins +
never-both, 401→force-refresh→retry, stops after one retry, resolver error
surfaced. Local run check (real binary + mock OAuth + mock OpenAI-compatible LLM
requiring the bearer): with a login, `zero exec` sends Bearer <oauth-token> and
gets a completion; without a login it falls back to the (wrong) API key and the
mock returns 401 — proving the OAuth path.

Gates: gofmt/vet/build(host+linux+windows)/test -race/staticcheck(no new)/
govulncheck(0)/deadcode(no new) all pass.

* providers: add ChatGPT-subscription-via-proxy preset + OAuth/subscriptions docs

Follows verified research (cross-checked across ~30 sources) into using ChatGPT
and Claude subscriptions: a subscription OAuth token does NOT work as a standard
bearer on the public APIs. OpenAI's token only works against the Cloudflare-gated
ChatGPT backend (headless clients get cf-mitigated 403); Anthropic rejects
third-party subscription OAuth, requires Claude-Code identity spoofing, 400s on
tool calls (Max overage lane), and is contractually banned with active
enforcement. So zero does NOT call those backends or spoof those clients.

Instead, the supported pattern is a local proxy that holds the subscription
session and exposes a clean OpenAI/Anthropic-compatible endpoint on 127.0.0.1;
zero points at it (it already supports custom base URLs, and C1 supplies OAuth
bearer for gateways that issue standard tokens).

  - providercatalog: add `chatgpt-proxy` ("ChatGPT (local OAuth proxy)") — an
    OpenAI-compatible, local, no-API-key preset defaulting to the established
    proxy port (http://localhost:10531/v1), base URL overridable. Claude uses the
    existing `custom-anthropic-compatible` entry (no canonical Claude-proxy port
    to hardcode — not invented).
  - docs/oauth-subscriptions.md: full recipe — built-in OAuth login (C1), the
    verified reason subscriptions need a proxy, copy-paste config for ChatGPT and
    Claude via a local proxy, and the sanctioned alternatives (API key; spawning
    the real `claude` CLI for Anthropic subscription automation).

Local run check: a config using `catalogID: chatgpt-proxy` (base URL pointed at a
mock OpenAI-compatible proxy) routes `zero exec` through the proxy with no auth
header and returns a completion.

Gates: gofmt/vet/build(host+linux+windows)/test/staticcheck(clean)/govulncheck(0)/
deadcode(no new) all pass. No new module deps.

* provideroauth: OpenRouter browser PKCE login (mints an API key) + `zero auth openrouter`

Adds the cleanest real OAuth login surfaced by the provider survey: OpenRouter's
loopback PKCE flow. It needs no client_id (public), uses S256, and the exchange
mints a normal OpenRouter API key — so no token-spoofing or vendor-backend gating
is involved (unlike ChatGPT/Claude subscriptions).

  - internal/provideroauth.OpenRouterLogin: binds a 127.0.0.1 loopback, opens/prints
    https://openrouter.ai/auth?callback_url=…&code_challenge=…&code_challenge_method=S256,
    captures the code, and POSTs it + the PKCE verifier to
    /api/v1/auth/keys → {key}. PKCE binds the code, so no separate CSRF state is
    needed. Injectable base URL / HTTP client / browser-opener for tests.
  - cli: `zero auth openrouter` runs the flow and prints the minted key with usage
    guidance. ZERO_OPENROUTER_BASE_URL overrides the endpoint (self-hosted/tests).

Tests: full flow over httptest (mint, missing-code, non-2xx exchange, empty key,
browser-error). Local run check via the real binary against a mock: the command
emits the correct PKCE authorize URL, the loopback callback is captured, and the
key is minted end-to-end.

First of the in-wizard OAuth providers; the wizard "Log in with OAuth" option
(next commit) calls this same flow and saves the key to the profile.

Gates: gofmt/vet/build(host+linux+windows)/test -race/staticcheck(no new)/
govulncheck(0)/deadcode(no new) all pass. No new module deps.

* oauth: built-in provider presets + xAI (Grok) preset

Adds an overridable preset layer to the env-driven OAuth registry so well-known
providers work without per-field env configuration. ResolveConfig now seeds each
field from a baked-in preset and lets the matching ZERO_OAUTH_<NAME>_* env var
override it (env wins); flow falls back to the preset then loopback.

First preset: xAI (Grok) — standard OIDC, PUBLIC client (no secret), loopback +
device. The access token is accepted directly as a bearer on api.x.ai/v1 (an
OpenAI-compatible endpoint), so the existing C1 wiring uses it for an xai provider
profile — no header/identity spoofing. `zero auth login xai` now works out of the
box. CAVEATS (documented in code + help): the client_id is a public Grok-CLI
client not formally documented by xAI (may change; override via env), and use
requires a SuperGrok / X Premium+ subscription.

Tests: preset resolution, env-overrides-preset (per field + flow), and that a
provider with neither preset nor env still errors. Local run check: `zero auth
login xai --device` (endpoints pointed at a mock, client_id NOT overridden) logs
in and the mock confirms the preset client_id b1a00492-… was sent.

Gates: gofmt/vet/build(host+linux+windows)/test/staticcheck(no new)/govulncheck(0)/
deadcode(no new) all pass. No new module deps.

* tui: in-wizard "Log in with OAuth" option (OpenRouter) + browser opener

Adds the credential-step OAuth login the setup wizard was missing. For a provider
that supports a usable browser OAuth flow (currently OpenRouter), the credential
screen now offers "ctrl+o to log in with OAuth in the browser (no key needed)"
alongside pasting a key.

Flow: ctrl+o → the wizard marks itself oauthPending and dispatches an async
tea.Cmd that runs provideroauth.OpenRouterLogin (opens the browser via the new
internal/browser opener, captures the loopback callback, mints a key). While it
runs the step shows "Opening your browser… (or run: zero auth openrouter)";
Esc cancels. On success the minted key fills the credential and the wizard
advances to model selection; on failure the redacted error is shown and the user
can retry or paste a key.

  - internal/browser: cross-platform OpenURL (open / xdg-open / rundll32) with a
    pure, unit-tested per-OS command builder.
  - provider_wizard: oauthPending/oauthErr state, providerWizardSupportsOAuth
    (OpenRouter only — ChatGPT/Claude use the proxy preset, others paste a key),
    the async cmd + result msg, and credential-step render/keys.
  - model.Update routes the OAuth result message.

Tests: openCommand per-OS; supportsOAuth (openrouter yes / openai no); ctrl+o
starts OAuth for OpenRouter and is a no-op otherwise; apply-success advances +
applies the key; apply-error stays + surfaces the message; render shows the hint
and the pending state. The interactive browser flow itself is the same one C3
verified end-to-end against a mock.

Gates: gofmt/vet/build(host+linux+windows)/test -race/staticcheck(no new — the
SA1019 mouse nits are pre-existing on origin/main)/govulncheck(0)/deadcode(no new).
No new module deps.

* docs: cover the built-in OpenRouter + xAI OAuth logins

Document the two turnkey OAuth providers added in this branch (OpenRouter browser
key-mint via `zero auth openrouter` / wizard ctrl+o; xAI via `zero auth login
xai`), alongside the existing subscription-proxy and custom-provider guidance.

* tui: method-first provider setup — a prominent "Sign in with OAuth" path

Reworks `/provider` setup to lead with a connect-method chooser, modeled on Codex
/ Claude Code / gemini-cli (verified via a UX survey), so OAuth is a first-class,
separate option instead of a key buried at the credential step.

New first step "How do you want to connect?":
  ❯ Sign in with OAuth                    (default when any OAuth provider exists)
      One-click browser login, no API key to copy (OpenRouter, xAI).
    Paste an API key / browse providers
      Any of 20+ providers, a local model, or a subscription via proxy.

  - OAuth path → a dedicated list of ONLY OAuth-capable providers
    (providercatalog.OAuthProviders()), each with a mode badge; selecting one
    runs the browser login (OpenRouter mints a key; xAI/preset providers store a
    refreshable token via the engine) and jumps straight to model selection,
    skipping the key/endpoint steps. A waiting screen shows progress + a CLI
    fallback; Esc cancels; errors are redacted and retryable.
  - Browse path → the existing full catalog → key/endpoint/model, unchanged.
  - ChatGPT/Claude are NOT offered as in-app OAuth (they can't work) — they remain
    reachable as the proxy/custom providers under "browse".

  - providercatalog: Descriptor gains OAuth/OAuthMintsKey/OAuthDeviceFlow; openrouter
    + xai are flagged; OAuthProviders() is the single source of truth.
  - wizard: new method step + OAuth provider list + per-provider OAuth dispatch
    (reuses provideroauth.OpenRouterLogin and the oauth engine login), waiting/
    render/keys/step-line/retreat all updated; mouse + keys ignored while a login
    is in flight.

Tests: catalog OAuth flags + OAuthProviders(); method chooser OAuth vs browse
paths; OAuth dispatch from the list; retreat→method; render shows the chooser +
provider list; existing wizard tests updated for the new first step. All tui tests
pass under -race.

Gates: gofmt/vet/build(host+linux+windows)/test/staticcheck(no new)/govulncheck(0)/
deadcode(no new) all pass. No new module deps.

* docs: document the /provider "Sign in with OAuth" method chooser

* tui: mirror the "Sign in with OAuth" method chooser in first-run onboarding

First-run setup (onboarding.go) was a separate flow that still showed its bare
provider list. Add the same connect-method chooser so new users get the prominent
OAuth path too, keeping /provider and first-run as one mental model.

  - New setupStageMethod (after Welcome): "How do you want to connect?" with
    "Sign in with OAuth" (default) and "Paste an API key / browse providers".
  - OAuth path filters the provider list to OAuth-capable providers, runs the same
    browser login (OpenRouter mints a key → captured as the setup credential; xAI
    stores a refreshable token and the profile is named after the provider so the
    runtime resolver attaches the bearer), shows a waiting screen, then jumps to
    model selection (skipping endpoint/name/credentials). Esc cancels; errors are
    redacted.
  - Browse path is the existing flow, unchanged. setupStages()/navigation/back/
    progress/footer/mouse all updated; OAuth result routed via model.Update.

Tests: method chooser OAuth path (OAuth-only list + retreat clears it) and
apply-OAuth-success advances to model with the minted key; existing onboarding
tests updated for the new step (pressSetupContinue transparently skips it). All
tui tests pass under -race.

Gates: gofmt/vet/build(host+linux+windows)/test/staticcheck(no new)/govulncheck(0)/
deadcode(no new) all pass. No new module deps.

* catalog+tui: show ChatGPT/Claude in the OAuth chooser as local-proxy entries

The "Sign in with OAuth" chooser listed only the real-OAuth providers (OpenRouter,
xAI), so ChatGPT and Claude looked missing. They can't do real in-app OAuth — a
subscription token is Cloudflare-gated (ChatGPT) / ToS-restricted for third-party
use (Claude) — so instead of hiding them or faking a login, list them honestly as
"subscription · needs a local proxy" entries that route into proxy setup.

  - catalog: new Descriptor.OAuthProxy flag + OAuthProxyProviders(); mark
    chatgpt-proxy and a new claude-proxy (local Anthropic-compatible, keyless)
    entry. localAnthropic() helper. Proxy entries are not OAuth (no fake login).
  - wizard + onboarding: the OAuth list now appends the proxy entries after the
    real OAuth providers (catalog order keeps real ones first). Selecting a proxy
    entry leaves OAuth mode and drops into the normal browse flow with the proxy
    provider selected and its default URL/model pre-filled, so the user just
    points Zero at the local proxy they run. No browser login is launched.
  - providerWizardNeedsEndpoint now true for proxy entries (the local port
    varies); faint badge "subscription · needs a local proxy".
  - docs/oauth-subscriptions.md: chooser now shows the four entries and explains
    the proxy routing.

Tests: catalog classification (OAuthProviders vs OAuthProxyProviders, flags);
wizard + onboarding proxy-entry routing (leaves OAuth mode, starts no login,
lands on endpoint with the URL pre-filled). Full suite green under -race;
staticcheck/govulncheck/deadcode clean. No new deps.

* oauth+tui: add xAI device-code (RFC 8628) login to the OAuth chooser

The OAuth chooser only did browser/loopback login, which can't work on a headless
or SSH box (no browser to open). Add device-code as a first-class option for
providers that support it (xAI).

  - oauth.Manager: PrepareDeviceLogin (request code) + CompleteDeviceLogin (poll +
    store), splitting the monolithic device flow so the UI can display the
    user_code + verification URI while it waits. CLI Login(Device:true) unchanged.
  - tui (shared oauth_device.go): oauthPreferDeviceFlow() picks device by default
    on SSH / headless Linux (no DISPLAY) or when ZERO_OAUTH_DEVICE is set;
    oauthDevicePrepare/Complete drive the two phases off the UI goroutine.
  - /provider wizard + first-run onboarding: device-capable providers show
    "Enter sign in · d device code" — Enter uses the browser (or device if
    headless), "d" forces device. The waiting screen shows the code + URL to
    enter elsewhere, then polls; success advances to model selection. Esc cancels;
    errors are redacted; device state is cleared on success/cancel/back.
  - docs: document the device-code path and ZERO_OAUTH_DEVICE.

Tests: manager Prepare/Complete two-phase; wizard + onboarding 'd' shortcut,
device-code display + poll dispatch, error surfacing, and success clearing device
state. Full suite green under -race; staticcheck/govulncheck/deadcode clean. No
new deps.

* tui: authenticate model discovery with the OAuth token so the live list shows

After an xAI sign-in the bearer lives in the token store, not as a pasted key, so
the post-login model step ran /models with no credential → it always fell back to
the curated list (with a discovery error). Now the user picks from the real live
model list after OAuth, like other CLIs.

  - oauthStoredToken() resolves a fresh stored token for a token-login provider.
  - wizard + onboarding discovery resolve and inject that token into the discovery
    profile inside the async goroutine (token refresh is network I/O); OpenRouter
    still uses its minted key, API-key users are unchanged. Discovery timeout
    bumped 8s→12s to cover an on-demand refresh.
  - The resolved token is added to the redaction secret set so it can never leak
    into a surfaced discovery error. If discovery still fails (offline), the
    curated per-provider fallback list is shown as before — the user always gets a
    pickable list.

Tests: oauthStoredToken reads a seeded login / returns empty when absent;
wizard discovery injects the stored token into the /models profile. Hermetic via
ZERO_OAUTH_TOKENS_PATH. Full suite green under -race; staticcheck/govulncheck/
deadcode clean. No new deps.

* catalog+tui: drop ChatGPT/Claude from the OAuth chooser — OpenRouter + xAI only

Reverts the proxy-entry listing: the "Sign in with OAuth" chooser now shows only
the providers that do real OAuth (OpenRouter, xAI). ChatGPT/Claude looked
confusing there since they can't actually sign in — they remain reachable via
"Paste an API key / browse providers" + the local-proxy recipe in
docs/oauth-subscriptions.md.

  - catalog: remove the Descriptor.OAuthProxy flag, OAuthProxyProviders(), the
    oauthProxyProvider/localAnthropic helpers, and the claude-proxy entry.
    chatgpt-proxy stays as a plain browse/config entry (its pre-existing role).
  - wizard + onboarding: OAuth list = OAuthProviders() again; removed the
    proxy→browse routing, the OAuthProxy badge, and the OAuthProxy endpoint rule.
  - device-code + OAuth-token model discovery (the later features) are untouched.
  - docs + tests updated to the two-entry chooser.

Gates: gofmt/vet/build(host+linux+windows)/test -race/staticcheck/deadcode all
clean. No new deps.

* cli: recognize a stored OAuth login as a provider credential

A provider authenticated via OAuth (e.g. `zero auth login xai`) has no inline
key or env var, so setupRequired treated it as unconfigured and forced onboarding
on every launch — and the active provider could be passed over for a different
one. setupRequired now also counts a stored OAuth login (read from the token
store) as a credential, so an OAuth-logged-in provider stays usable without
onboarding.

Tests: providerHasOAuthLogin (by name/catalog id); setupRequired returns false
for an OAuth-logged-in provider, true for a keyless one with no login.

* Address review feedback on OAuth provider login

Resolve CodeRabbit review comments on this PR:

- PrepareDeviceLogin: self-bound the network prepare (endpoint discovery +
  device-code request) with opts.Timeout (default 5m) like Login, so a caller
  passing an unbounded context still gets the timeout guarantee.
- CompleteDeviceLogin: bound the poll by the device code's own ExpiresAt so an
  unbounded caller context cannot leave an in-flight request hanging past the
  code's lifetime (PollDeviceToken already re-checks between polls).
- OAuthProviders(): return cloned descriptors, matching All()/the other
  accessors, so callers can't mutate the shared catalog backing slices.
- Onboarding applySetupOAuth / applySetupOAuthDeviceCode: ignore a stale result
  for a provider the user has since switched away from, so a late login can't
  capture a credential against the wrong provider.
- Onboarding: surface a failed OAuth login on the provider list the user lands
  on (the waiting screen that owned the error is gone once oauthPending clears),
  so the failure isn't silent and the user can retry.
- Onboarding: hide the "Sign in with OAuth" method when this setup's own
  provider list has no OAuth-capable entry, so it can't be selected into an
  empty provider list (setupMethodOptions).
- docs/oauth-subscriptions.md: label the bare example fences (text) and update
  the Anthropic policy section for the April 4 2026 third-party cutoff.

Adds tests for the descriptor-clone isolation and the method-option filtering.

* oauth: gate built-in presets behind ZERO_OAUTH_ALLOW_PRESETS

The xAI preset baked a third-party OAuth client_id into the default
credential path, which breaks the engine's documented invariant that no
third-party OAuth client identity is used unless the operator configures
it. Rather than drop the convenience entirely, make presets opt-in:

- ResolveConfig only consults builtinOAuthPresets when ZERO_OAUTH_ALLOW_PRESETS
  is truthy (presetsAllowed); by default every field must come from
  ZERO_OAUTH_<NAME>_* env, so the binary's preset client_id is never used
  without an explicit opt-in.
- The "not configured" error now points the user at ZERO_OAUTH_ALLOW_PRESETS
  when a preset exists, so the wizard/CLI failure is self-documenting.
- Refresh the package, Registry, and preset docs to describe the opt-in;
  env still overrides any preset field.
- docs/oauth-subscriptions.md: document the flag for one-click xAI sign-in.

Keeps both: invariant-clean by default, one-click xAI for opted-in users.
Tests: preset resolution now requires the opt-in; added a gate-off test
asserting xAI stays inert (and the error names the flag) without it.

* oauth: read a stored token without resolving provider config

GetFresh resolved the provider OAuth config for every read via loadForKey,
even when the stored token was still valid and no refresh would happen.
With presets gated behind ZERO_OAUTH_ALLOW_PRESETS, that made a stored xAI
login unreadable (and stalled the proactive refresh scheduler) unless the
opt-in flag was set at read time — config that is only needed to refresh.

Split loadForKey into loadToken (token only) and resolveConfigForKey
(refresh-only). GetFresh now returns a still-valid token without touching
the config; only an actual refresh (or Handle401) resolves endpoints +
client. The scheduler's load uses loadToken too, so proactive refresh
survives a gated preset and only the refresh attempt needs the config.

Fixes the Smoke failures TestOAuthStoredTokenReadsStoredLogin and
TestProviderWizardDiscoveryUsesOAuthToken (a fresh stored token must be
readable without the preset opt-in).

* Address CodeRabbit re-review round 2 on OAuth provider login

- providerio SendWithAuthRetry: resolve the auth headers BEFORE dispatching, so
  a resolver error returns immediately instead of letting SendWithRetry send an
  unauthenticated request (leaking the path/body).
- tui handleMouse: ignore all mouse input (clicks + wheel) while a provider-wizard
  OAuth login is pending, so a stray scroll can't change the selected provider
  mid-login (mirrors the keyboard pending guard; the setup mouse handler already
  did this).
- cli runAuthOpenRouter: reject unexpected args/flags (e.g. --json) instead of
  silently running the login, matching the other auth subcommands.
- cli auth help: align the preset wording with the opt-in gate — openrouter works
  out of the box; xai needs ZERO_OAUTH_ALLOW_PRESETS=1 or ZERO_OAUTH_XAI_*.
- onboarding_test: compute selectedMethod from m.setupMethodOptions() (the
  setup-filtered list) instead of providerWizardMethodOptions(), which could index
  past the end when OAuth is hidden.
- docs/oauth-subscriptions.md: sharpen the Anthropic timeline — Feb 19 2026 docs
  update, then the April 4 2026 enforcement that stopped subscription OAuth tokens
  working in third-party harnesses; current path is an API key or pay-as-you-go.

Adds tests for the no-dispatch-on-resolver-error path and openrouter arg rejection.

* Address CodeRabbit re-review round 3 on OAuth provider login

Two findings missed in the previous round:

- oauth manager resolveConfigForKey: resolve issuer metadata before refreshing.
  It returned ResolveConfig's result without calling resolveEndpoints, so a
  provider configured with only ZERO_OAUTH_<NAME>_ISSUER_URL (no TOKEN_URL) had
  an empty token endpoint and could not refresh on GetFresh/Handle401. Thread the
  context through and run discovery; adds a test that an issuer-only provider
  refreshes via the discovered token endpoint.
- provider_wizard_oauth_test: assert openrouter is actually present in the OAuth
  list before selecting it, so the test can't silently pass against a different
  provider if openrouter is renamed/removed.

(The re-flagged PrepareDeviceLogin timeout is already handled — it self-bounds
with opts.Timeout, same as Login.)

* Address CodeRabbit re-review round 4 (nitpicks) on OAuth provider login

- presets scopesOrPreset: return a copy of the preset scopes so a caller
  appending to cfg.Scopes can't mutate the shared global preset slice.
- provideroauth/openrouter: use bytes.NewReader(payload) instead of
  strings.NewReader(string(payload)) (avoid a needless []byte->string copy).
- onboarding setupCredentialSummary: show "OAuth token" on the Ready screen for
  an OAuth token-login provider (e.g. xAI) instead of "env var XAI_API_KEY" — it
  is authenticated by a stored token, not a key. Key-minting OAuth (OpenRouter)
  still resolves to a saved key.

(The flagged "Registry doc says no built-ins" was already corrected in the
preset opt-in commit — the doc now describes the ZERO_OAUTH_ALLOW_PRESETS gate.)

Adds tests for the scope-slice copy and the OAuth-token credential summary.
2026-06-16 15:33:36 +05:30
Vasanthdev2004 e06ee74190 Split Ollama cloud and local providers 2026-06-11 00:54:49 +05:30
Vasanth T 2a8dc75628 Add provider catalog foundation (#141)
* feat: add provider catalog foundation

* feat: add provider onboarding checks

* fix: address provider readiness review

* fix: clear provider catalog review threads

* fix: accept direct auth values in provider checks

* fix: harden provider catalog config

* fix: guard project provider credential binding
2026-06-08 16:37:29 +05:30