@legioncodeinc/rflectr 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +1 -5
  2. package/dist/cli.js +4 -1
  3. package/dist/cli.js.map +1 -0
  4. package/package.json +4 -1
  5. package/.markdown-link-check.json +0 -7
  6. package/AGENTS.md +0 -169
  7. package/assets/733630021_1421561133353555_3999689754075308337_n.jpg +0 -0
  8. package/assets/github-home-image.png +0 -0
  9. package/assets/og-image.jpg +0 -0
  10. package/assets/og-image.png +0 -0
  11. package/assets/og-image.psd +0 -0
  12. package/assets/rflectr-no-bg.png +0 -0
  13. package/assets/vertex-models.example.json +0 -14
  14. package/library/README.md +0 -39
  15. package/library/issues/README.md +0 -46
  16. package/library/issues/backlog/README.md +0 -26
  17. package/library/issues/completed/README.md +0 -13
  18. package/library/issues/in-work/README.md +0 -13
  19. package/library/knowledge/README.md +0 -34
  20. package/library/knowledge/private/README.md +0 -40
  21. package/library/knowledge/private/ai/README.md +0 -8
  22. package/library/knowledge/private/ai/model-discovery-classification.md +0 -81
  23. package/library/knowledge/private/ai/translation-layer.md +0 -88
  24. package/library/knowledge/private/architecture/README.md +0 -10
  25. package/library/knowledge/private/architecture/launch-flow-claude.md +0 -93
  26. package/library/knowledge/private/architecture/system-overview.md +0 -108
  27. package/library/knowledge/private/auth/README.md +0 -9
  28. package/library/knowledge/private/auth/oauth-device-flows.md +0 -95
  29. package/library/knowledge/private/data/README.md +0 -8
  30. package/library/knowledge/private/data/preferences-config.md +0 -87
  31. package/library/knowledge/private/data/provider-registry.md +0 -126
  32. package/library/knowledge/private/infrastructure/README.md +0 -7
  33. package/library/knowledge/private/infrastructure/server-gateway.md +0 -87
  34. package/library/knowledge/private/integrations/README.md +0 -8
  35. package/library/knowledge/private/integrations/harnesses.md +0 -87
  36. package/library/knowledge/private/integrations/local-proxy.md +0 -82
  37. package/library/knowledge/private/security/README.md +0 -9
  38. package/library/knowledge/private/security/credential-storage.md +0 -129
  39. package/library/knowledge/private/standards/documentation-framework.md +0 -154
  40. package/library/knowledge/public/README.md +0 -49
  41. package/library/knowledge/public/faqs/README.md +0 -7
  42. package/library/knowledge/public/faqs/troubleshooting.md +0 -92
  43. package/library/knowledge/public/guides/README.md +0 -13
  44. package/library/knowledge/public/guides/ai-agents.md +0 -273
  45. package/library/knowledge/public/guides/api-server.md +0 -108
  46. package/library/knowledge/public/guides/claude-desktop.md +0 -382
  47. package/library/knowledge/public/guides/codex.md +0 -296
  48. package/library/knowledge/public/guides/gemini-cli.md +0 -105
  49. package/library/knowledge/public/guides/model-compatibility.md +0 -80
  50. package/library/knowledge/public/guides/providers.md +0 -90
  51. package/library/knowledge/public/overview/README.md +0 -7
  52. package/library/knowledge/public/overview/what-is-rflectr.md +0 -71
  53. package/library/notes/README.md +0 -21
  54. package/library/requirements/README.md +0 -51
  55. package/library/requirements/backlog/README.md +0 -30
  56. package/library/requirements/completed/README.md +0 -14
  57. package/library/requirements/completed/prd-001-cli-core-launch-orchestration/prd-001-cli-core-launch-orchestration-index.md +0 -205
  58. package/library/requirements/completed/prd-001-cli-core-launch-orchestration/qa/.gitkeep +0 -0
  59. package/library/requirements/completed/prd-002-provider-registry/qa/.gitkeep +0 -0
  60. package/library/requirements/completed/prd-003-model-discovery-classification/qa/.gitkeep +0 -0
  61. package/library/requirements/completed/prd-004-translation-layer/qa/.gitkeep +0 -0
  62. package/library/requirements/completed/prd-005-local-proxy-catalog-routing/qa/.gitkeep +0 -0
  63. package/library/requirements/completed/prd-007-oauth-device-flows/qa/.gitkeep +0 -0
  64. package/library/requirements/completed/prd-011-claude-desktop-integration/qa/.gitkeep +0 -0
  65. package/library/requirements/in-work/README.md +0 -19
  66. package/library/requirements/reports/README.md +0 -31
  67. package/scripts/refresh-models-dev-cache.mjs +0 -34
  68. package/test-proxy.ts +0 -19
  69. package/test-split.js +0 -1
@@ -1,88 +0,0 @@
1
- # The Translation Layer
2
-
3
- > Category: Ai | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- The single path that lets a Claude Code / Codex / Gemini host talk to any non-Anthropic model. This doc explains the Vercel AI SDK adapter (`src/sdk-adapter.ts`) and the provider factory (`src/provider-factory.ts`) that feeds it. Read [`../architecture/system-overview.md`](../architecture/system-overview.md) first.
6
-
7
- **Related:**
8
- - [`model-discovery-classification.md`](model-discovery-classification.md)
9
- - [`../integrations/local-proxy.md`](../integrations/local-proxy.md)
10
- - [`../integrations/harnesses.md`](../integrations/harnesses.md)
11
- - Source: `src/sdk-adapter.ts`, `src/provider-factory.ts`, `src/proxy-shared.ts`, `src/codex-responses-adapter.ts`, `src/gemini-proxy.ts`
12
-
13
- ---
14
-
15
- ## Why there is exactly one translation path
16
-
17
- A naive launcher would hand-roll a translator per provider: Anthropic→OpenAI, Anthropic→Gemini, and so on. That is a combinatorial mess and every provider has quirks (message ordering, reasoning signatures, tool-call encoding). `rflectr` instead routes **all** non-Anthropic providers through the Vercel AI SDK (`ai` + `@ai-sdk/*`) — the same packages OpenCode loads. The SDK owns wire format, endpoint selection, and provider quirks, so there is one translation path, not N.
18
-
19
- ```mermaid
20
- flowchart TD
21
- host["Host wire format (Anthropic / Responses / Gemini)"]
22
- host --> trans["translate*Request() — host body → SDK call params"]
23
- trans --> factory["createLanguageModel({npm, modelId, apiKey, baseURL})"]
24
- factory --> model["Vercel AI SDK LanguageModel"]
25
- model --> stream["streamText / generateText"]
26
- stream --> back["map SDK fullStream → host SSE/response"]
27
- back --> host
28
- ```
29
-
30
- The rule that decides whether to translate at all: `isSdkMigratedNpm(npm)` (`src/provider-factory.ts`) is true for **any** npm except `@ai-sdk/anthropic`. Anthropic-format models skip the adapter and are forwarded raw.
31
-
32
- ---
33
-
34
- ## provider-factory.ts — npm → LanguageModel
35
-
36
- `createLanguageModel(spec)` (async) is the factory. `spec` carries `{ npm, modelId, apiKey, baseURL?, providerId?, authType?, oauthAccountId?, vertex? }`. It dynamically `import(npm)`s the SDK package and discovers its `create*` factory. The router has special branches:
37
-
38
- | npm | Behaviour |
39
- |---|---|
40
- | `@ai-sdk/google-vertex/anthropic` (`VERTEX_ANTHROPIC_NPM`) | Claude on Google Vertex AI via gcloud Application Default Credentials (no apiKey). |
41
- | `@ai-sdk/openai` | OAuth → ChatGPT Codex backend (`https://chatgpt.com/backend-api/codex`); API key → direct OpenAI. `modelPrefersResponsesApi()` picks `openai.responses(id)` vs `openai.chat(id)`. |
42
- | `@ai-sdk/xai` | Direct; also consults `modelPrefersResponsesApi()`. |
43
- | `@ai-sdk/google` | Direct — ignores `baseURL`, uses the native `/v1beta` endpoint. |
44
- | `@ai-sdk/anthropic` | Direct; strips a trailing `/v1` from `baseURL` if present. |
45
- | `@ai-sdk/openai-compatible`, `@openrouter/ai-sdk-provider` | Routed via `baseURL`. |
46
- | anything else | `loadSdkProviderFactory(npm)` finds the `create*()` export dynamically. |
47
-
48
- The `@ai-sdk/*` provider packages ship as npm **`dependencies`** and are marked `external` in `tsup.config.ts`, so they resolve from `node_modules` at runtime and keep `dist/cli.js` small.
49
-
50
- ### The Responses API selector
51
-
52
- `modelPrefersResponsesApi(modelId)` returns true for OpenAI/xAI models that require the Responses API rather than Chat Completions: GPT-5.4+, GPT-5.5, `gpt-5-pro` / `gpt-5.2-pro`, `*-codex`, the o-series (`o3`, `o4`, …), and xAI `grok-*-multi-agent`. Newer OpenAI reasoning models only round-trip correctly through Responses, so this selection is load-bearing — not cosmetic.
53
-
54
- OpenCode catalog ids may differ from upstream API ids (e.g. `gpt-5.5-fast` → `gpt-5.5`); `upstreamModelId` carries OpenCode's `api.id` for the actual upstream call.
55
-
56
- ### Reasoning capabilities
57
-
58
- `getReasoningCapabilities(npm, modelId, metadata)` returns a `ReasoningCapabilities` describing whether a model exposes controllable effort/thinking, internal-only reasoning, or none — covering Claude 4.6+, Gemini 2.5+/3, Mistral reasoners, xAI reasoners, DeepSeek V4, Kimi, and OpenRouter. The adapter uses this (with `effortProviderOptions` / `thinkingProviderOptions` / `deepMergeProviderOptions`) to translate a host's thinking/effort request into the right provider option block.
59
-
60
- ---
61
-
62
- ## sdk-adapter.ts — Anthropic ↔ SDK
63
-
64
- The adapter handles the Claude-Code-facing direction. Its contract: **one turn per request.** Claude Code owns the tool loop, so the adapter never loops; it translates a single request and streams a single response.
65
-
66
- - `translateRequest(body, npm, options?)` builds the SDK call params from an Anthropic request — messages, tools, `tool_choice`, system. Critically, it **folds inline `role:'system'` messages into the system prompt**: Claude Code injects the skills list and system-reminders as system-role messages mid-conversation, and dropping them would break behaviour. `TranslateRequestOptions` carries `defaultEffort` (fallback when the client omits effort, e.g. the Claude Desktop gateway), `reasoningMetadata`, and `openAiOAuth` (ChatGPT Codex OAuth manages its own output limit and requires `instructions`).
67
- - `streamAnthropicResponse` maps the SDK `fullStream` to Anthropic SSE.
68
- - `generateAnthropicResponse` handles the non-streaming case.
69
-
70
- ### The thought_signature round-trip
71
-
72
- Reasoning models (especially Gemini) require their `thought_signature` to be echoed back verbatim on the next turn. Anthropic's wire format has no field for it, so `rflectr` smuggles it through the tool-use id:
73
-
74
- - **Encode:** `encodeToolUseId(rawId, signature)` (`src/proxy-shared.ts`) produces `{id}::ts::{signature}`.
75
- - **Decode:** `splitToolUseId(id)` recovers `{ rawId, thoughtSignature }`, which is fed back into `providerOptions.google.thoughtSignature`.
76
-
77
- Gemini puts the signature on tool-call parts (captured at `tool-input-start`); the SDK then handles Gemini's strict echo-back. This is the reason the old hand-rolled Gemini-native path was retired. The `::ts::` separator would break only if a signature literally contained `::ts::` — extremely unlikely, and a documented edge.
78
-
79
- ---
80
-
81
- ## The other two host directions
82
-
83
- The same factory + SDK model is reused; only the host-facing translation differs:
84
-
85
- - **Codex Responses API** (`src/codex-responses-adapter.ts`): `translateResponsesRequest` / `translateResponsesInput` / `translateResponsesTools` build SDK params from a Responses body; `streamResponsesResponse` / `generateResponsesResponse` emit the Responses SSE/JSON shape.
86
- - **Gemini REST** (`src/gemini-proxy.ts` + `src/gemini-parts.ts`): `translateGeminiRequest` extracts system instruction, contents, tools, and generation config; `parseGeminiPart` / `collectAnthropicBlocksFromGeminiParts` / `mapGeminiUsage` translate parts and usage.
87
-
88
- All three converge on `createLanguageModel` + `streamText`/`generateText`. That convergence is the whole point: add a provider once and every host can use it.
@@ -1,10 +0,0 @@
1
- # Architecture
2
-
3
- System-level design docs and Architecture Decision Records (ADRs).
4
-
5
- | Doc | Covers |
6
- |---|---|
7
- | [`system-overview.md`](system-overview.md) | What rflectr is, its surfaces, the shared translation core, env isolation. **Start here.** |
8
- | [`launch-flow-claude.md`](launch-flow-claude.md) | The `rflectr claude` flow: single-model vs switch-menu launch paths. |
9
-
10
- ADRs (when added) live here as `ADR-<n>-<kebab-slug>.md`.
@@ -1,93 +0,0 @@
1
- # Claude Code Launch Flow
2
-
3
- > Category: Architecture | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- How `rflectr claude` goes from a command line to a running Claude Code process pointed at the chosen model. Read [`system-overview.md`](system-overview.md) first. This doc traces `runClaudeCommand` in `src/cli.ts` and its two launch paths.
6
-
7
- **Related:**
8
- - [`system-overview.md`](system-overview.md)
9
- - [`../integrations/local-proxy.md`](../integrations/local-proxy.md)
10
- - [`../ai/translation-layer.md`](../ai/translation-layer.md)
11
- - [`../data/preferences-config.md`](../data/preferences-config.md)
12
- - Source: `src/cli.ts` (`runClaudeCommand`, `runModelsCommand`, `launchClaudeViaCatalog`), `src/env.ts` (`buildChildEnv`), `src/catalog.ts`
13
-
14
- ---
15
-
16
- ## The two modes
17
-
18
- The launch has two shapes, decided by one line in `runClaudeCommand`:
19
-
20
- ```ts
21
- const switchMenuActive = favorites.length > 0 && !launchPlan.skip;
22
- ```
23
-
24
- - **Single-model mode** (no favorites saved): one model, one route. Launch and exit.
25
- - **Switch-menu mode** (`rflectr models` has saved at least one favorite): a multi-route catalog proxy is started and Claude Code's gateway model discovery is enabled, so the in-session `/model` command lists the starting model plus every favorite for live switching.
26
-
27
- Favorites are managed by `runModelsCommand` (also in `src/cli.ts`) and persisted to `~/.rflectr/config.json`. The cap is `MAX_MODEL_CATALOG = 20` (`src/constants.ts`).
28
-
29
- ---
30
-
31
- ## Single-model flow
32
-
33
- ```mermaid
34
- flowchart TD
35
- start["rflectr claude"] --> bin["findClaudeBinary()"]
36
- bin --> firstrun["needsFirstRunSetup? → runFirstRunWizard()"]
37
- firstrun --> catalog["fetchProviderCatalog()"]
38
- catalog --> pickP["p.select: which provider?"]
39
- pickP --> pickM["pickLocalModel(provider)"]
40
- pickM --> fmt{"selectedModel.modelFormat"}
41
- fmt -->|anthropic| direct["buildChildEnv(model.baseUrl, ...) — no proxy"]
42
- fmt -->|openai| proxy["startProxy(...) → buildChildEnv(127.0.0.1:port, ...)"]
43
- direct --> launch["launchClaude(env, model, args)"]
44
- proxy --> launch
45
- launch --> wait["Claude Code runs (stdio inherited)"]
46
- wait --> close["proxyHandle.close() on exit"]
47
- ```
48
-
49
- The format branch is the heart of it (`src/cli.ts`):
50
-
51
- - `modelFormat === 'anthropic'` → **direct passthrough.** `buildChildEnv(selectedModel.baseUrl!, selectedModel.id, launchApiKey, undefined, contextWindow)`. No proxy; Claude Code talks straight to the provider's Anthropic-compatible endpoint. `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` is set so beta headers are stripped on the direct hop.
52
- - otherwise → **SDK adapter proxy.** `startProxy(completionsUrl, modelId, trace, contextWindow, { npm, baseURL, upstreamModelId, providerId, authType, oauthAccountId, supportedParameters, reasoning, interleavedReasoningField }, apiKey)` returns a `ProxyHandle`; the child env points `ANTHROPIC_BASE_URL` at `http://127.0.0.1:<port>`.
53
-
54
- The provider API key is resolved by `resolveLocalProviderApiKey(activeProvider)` (`src/provider-catalog.ts`). If none is found, launch aborts with a message pointing at `rflectr providers`.
55
-
56
- ---
57
-
58
- ## Switch-menu (catalog) flow
59
-
60
- When favorites exist, `runClaudeCommand` delegates to `launchClaudeViaCatalog`:
61
-
62
- 1. `makeRouteResolver(localProviders, zenModels, goModels, zenGoApiKey)` (`src/catalog.ts`) builds a function that maps a `(providerId, modelId)` pair to a `ProxyRoute`.
63
- 2. `buildCatalogRoutes(startingRoute, favorites, resolveRoute)` builds the route list — **starting model + favorites only**, never the full catalog — and reports `droppedFavorites` (favorites whose provider/model is no longer available, silently skipped).
64
- 3. `startProxyCatalog(catalogRoutes, startingRoute.aliasId, trace)` starts one proxy serving all routes.
65
- 4. `buildChildEnv(..., enableGatewayDiscovery = true)` sets `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` so Claude Code fetches `/v1/models` from the proxy and populates `/model`.
66
-
67
- Each route's id is rewritten by `aliasModelId()` (`src/proxy.ts`) so Claude Code sees unique, gateway-safe names (e.g. `anthropic-opencode-go__deepseek-v4-flash`) in the picker. See [`../integrations/local-proxy.md`](../integrations/local-proxy.md).
68
-
69
- A `__favorites__` pseudo-provider is unshifted onto the provider picker in switch-menu mode; selecting it loads the Favorites Catalog and uses `resolveFirstAvailableFavorite` as the starting model.
70
-
71
- ---
72
-
73
- ## The context-window caveat
74
-
75
- In switch-menu mode the displayed context window reflects the **launch** model and does **not** update on a live `/model` switch. Claude Code's gateway model discovery only carries `id` + `display_name` (no `context_window`) and fetches `/v1/models` once at startup, so `CLAUDE_CODE_MAX_CONTEXT_TOKENS` — fixed at launch by `buildChildEnv` — is the only lever. Single-model launches show the correct window. This is a documented, by-design limitation.
76
-
77
- ---
78
-
79
- ## Flags and boot shortcuts
80
-
81
- `parseArgs` (`src/cli.ts`) recognises starter flags (`--dry-run`, `--setup`, `--trace`, `--help`, `--version`) and relay launch flags (`--provider`, `--model`). Everything after `--`, and any unrecognised flag, is forwarded verbatim to Claude Code (`claudeArgs`).
82
-
83
- - `--provider X --model Y` (or print mode `-p`) skips the wizard entirely via `planLaunchWizard` / `findProviderAndModel` (`src/launch-target.ts`).
84
- - `--dry-run` runs the whole wizard but prints a preview (`printDryRun`) and writes nothing — it ignores saved env keys, keyring, tier, and preferences, simulating a fresh first run.
85
- - `--trace` writes a debug log under `~/.rflectr/logs/` and prints errors on exit (`prepareClaudeTraceLog` / `printTraceLog`).
86
-
87
- Clean-stdout agent mode (`wantsCleanAgentStdout`, `setAgentStdoutMode`) suppresses the interactive intro/spinners when Claude Code is run in print/pipe mode.
88
-
89
- ---
90
-
91
- ## What the child process receives
92
-
93
- For the exact env contract (`ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL`, `CLAUDE_CODE_MAX_CONTEXT_TOKENS`, the tool-search / system-prompt compat vars, and the removed conflict vars), see [`../security/credential-storage.md`](../security/credential-storage.md#child-process-environment).
@@ -1,108 +0,0 @@
1
- # System Overview
2
-
3
- > Category: Architecture | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- Read this first. It explains what `rflectr` is, the surfaces it exposes, and the single translation core that every surface shares. New engineers should read this before diving into any domain doc.
6
-
7
- **Related:**
8
- - [`launch-flow-claude.md`](launch-flow-claude.md)
9
- - [`../ai/translation-layer.md`](../ai/translation-layer.md)
10
- - [`../ai/model-discovery-classification.md`](../ai/model-discovery-classification.md)
11
- - [`../integrations/local-proxy.md`](../integrations/local-proxy.md)
12
- - [`../integrations/harnesses.md`](../integrations/harnesses.md)
13
- - [`../data/provider-registry.md`](../data/provider-registry.md)
14
- - Source: `src/cli.ts`, `src/constants.ts`
15
-
16
- ---
17
-
18
- ## Why this exists
19
-
20
- The major agentic coding tools — Claude Code, OpenAI Codex (CLI and desktop), Google Gemini CLI, and Claude Desktop — each speak only to their vendor's own API by default. `rflectr` is a launcher that re-points each of those tools at a *different* model backend without the tool noticing. You can run Claude Code against a Groq Llama model, Codex against DeepSeek, or Gemini CLI against a local Ollama endpoint — picking the model from an interactive wizard, then handing the host tool an environment that makes it believe it is talking to its native API.
21
-
22
- The published npm package and CLI binary are both named `rflectr` (`package.json` is the single source of truth for the version; see [`CLAUDE.md`](../../../../CLAUDE.md) for the release workflow). The repository directory is `rflectr`.
23
-
24
- The hard problem `rflectr` solves is **wire-format translation**: Claude Code emits Anthropic `/v1/messages`, Codex emits the OpenAI Responses API, Gemini CLI emits the Gemini REST protocol — but the chosen model may speak any of those formats (or none of them directly). A local HTTP proxy sits between the host tool and the upstream model and translates in both directions. All non-Anthropic translation flows through one path: the **Vercel AI SDK adapter** (see [`../ai/translation-layer.md`](../ai/translation-layer.md)).
25
-
26
- ---
27
-
28
- ## The surfaces
29
-
30
- Every surface is a subcommand of the `rflectr` CLI, dispatched from `parseArgs` / `main` in `src/cli.ts`.
31
-
32
- | Command | What it launches | Host wire format | Doc |
33
- |---|---|---|---|
34
- | `rflectr claude` | Claude Code CLI | Anthropic `/v1/messages` | [`launch-flow-claude.md`](launch-flow-claude.md) |
35
- | `rflectr codex` | OpenAI Codex CLI | OpenAI Responses (`/v1/responses`) | [`../integrations/harnesses.md`](../integrations/harnesses.md) |
36
- | `rflectr codex-app` | Codex desktop app | OpenAI Responses | [`../integrations/harnesses.md`](../integrations/harnesses.md) |
37
- | `rflectr gemini` | Gemini CLI | Gemini REST (`:generateContent`) | [`../integrations/harnesses.md`](../integrations/harnesses.md) |
38
- | `rflectr claude-app` | Claude Desktop app | Anthropic (gateway config) | [`../integrations/harnesses.md`](../integrations/harnesses.md) |
39
- | `rflectr server` | Foreground API gateway | Anthropic + OpenAI compatible | [`../infrastructure/server-gateway.md`](../infrastructure/server-gateway.md) |
40
- | `rflectr providers` | Provider registry manager | — | [`../data/provider-registry.md`](../data/provider-registry.md) |
41
- | `rflectr models` / `favorites` | Favorite-model manager | — | [`launch-flow-claude.md`](launch-flow-claude.md) |
42
-
43
- ```mermaid
44
- flowchart TD
45
- subgraph hosts["Host tools (unmodified)"]
46
- cc["Claude Code"]
47
- cx["Codex CLI / app"]
48
- gm["Gemini CLI"]
49
- cd["Claude Desktop"]
50
- end
51
- subgraph relay["rflectr"]
52
- proxy["Local proxy 127.0.0.1:random"]
53
- adapter["Vercel AI SDK adapter"]
54
- registry["Provider registry ~/.rflectr/providers.json"]
55
- end
56
- subgraph upstream["Upstream models"]
57
- anth["Anthropic-format endpoints"]
58
- sdk["Any @ai-sdk provider (OpenAI, Groq, Gemini, xAI, ...)"]
59
- end
60
- cc -->|ANTHROPIC_BASE_URL| proxy
61
- cx -->|OPENAI base_url| proxy
62
- gm -->|GOOGLE_GEMINI_BASE_URL| proxy
63
- cd -->|gateway config| proxy
64
- proxy -->|"anthropic format"| anth
65
- proxy -->|"everything else"| adapter
66
- adapter --> sdk
67
- registry --> proxy
68
- ```
69
-
70
- ---
71
-
72
- ## The shared core
73
-
74
- Three subsystems are reused by every surface:
75
-
76
- **1. The provider registry** (`src/registry/`, `src/provider-catalog.ts`). The list of providers and their models lives in `~/.rflectr/providers.json`. It is the single source of truth for what shows up in every wizard. Built-in templates (Groq, Mistral, OpenAI, Ollama, …) are defined in `src/provider-templates.ts`; OpenCode Zen / Go are always available even with no registry. See [`../data/provider-registry.md`](../data/provider-registry.md).
77
-
78
- **2. The translation layer** (`src/sdk-adapter.ts`, `src/provider-factory.ts`). `createLanguageModel({ npm, modelId, apiKey, baseURL })` turns whatever npm package OpenCode/the registry assigned the provider into a Vercel AI SDK `LanguageModel`. The adapter then maps the host's wire format to and from that model, one turn per request — the host tool always owns its own tool loop. See [`../ai/translation-layer.md`](../ai/translation-layer.md).
79
-
80
- **3. The local proxy** (`src/proxy.ts` and the per-protocol variants `src/codex-proxy.ts`, `src/gemini-proxy.ts`). A throwaway HTTP server on `127.0.0.1:<random port>` that the host tool is pointed at. Anthropic-format models are forwarded raw; everything else goes through the SDK adapter. `aliasModelId()` rewrites non-`claude-*` ids to a gateway-safe form. See [`../integrations/local-proxy.md`](../integrations/local-proxy.md).
81
-
82
- ---
83
-
84
- ## Environment isolation, not config editing
85
-
86
- `rflectr` never edits the host tool's settings file. It launches the child process with a purpose-built environment (`buildChildEnv` in `src/env.ts`) that:
87
-
88
- - **Removes** the 17 conflicting Anthropic/Vertex/Bedrock/AWS/Foundry env vars listed in `CONFLICTING_ENV_VARS` (`src/constants.ts`), so stale cloud config can't leak in.
89
- - **Sets** `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL`, and `CLAUDE_CODE_MAX_CONTEXT_TOKENS`.
90
-
91
- This avoids the backup/restore problem that settings-file rewriters have. The one caveat: Claude Code itself persists the launched model to `~/.claude/settings.json`, so a later bare `claude` may still show a relay alias. See [`../security/credential-storage.md`](../security/credential-storage.md) for the full env contract.
92
-
93
- The two desktop apps (`claude-app`, `codex-app`) are the exception — they *do* write config files, because the apps have no env to inherit. Those writes are backed up and restored on exit via lock files. See [`../integrations/harnesses.md`](../integrations/harnesses.md).
94
-
95
- ---
96
-
97
- ## A critical URL constraint
98
-
99
- `BACKENDS.baseUrl` in `src/constants.ts` must **not** include `/v1`. The Anthropic SDK appends `/v1/messages` automatically, so `https://opencode.ai/zen/v1` would produce requests to `/zen/v1/v1/messages` → 404. The same rule applies anywhere an Anthropic-format `baseUrl` is built. This is the single most common configuration footgun in the codebase.
100
-
101
- ---
102
-
103
- ## Where to go next
104
-
105
- - To trace a launch end to end: [`launch-flow-claude.md`](launch-flow-claude.md).
106
- - To understand how a Groq or DeepSeek model gets spoken to as if it were Anthropic: [`../ai/translation-layer.md`](../ai/translation-layer.md).
107
- - To understand how the model list is built and classified: [`../ai/model-discovery-classification.md`](../ai/model-discovery-classification.md).
108
- - To understand credential handling: [`../security/credential-storage.md`](../security/credential-storage.md) and [`../auth/oauth-device-flows.md`](../auth/oauth-device-flows.md).
@@ -1,9 +0,0 @@
1
- # Auth
2
-
3
- Provider sign-in flows (distinct from credential storage, which is under `security/`).
4
-
5
- | Doc | Covers |
6
- |---|---|
7
- | [`oauth-device-flows.md`](oauth-device-flows.md) | RFC 8628 device flows for OpenAI/ChatGPT, xAI/Grok, GitHub Copilot; token storage and refresh; PKCE. |
8
-
9
- For where keys/tokens are stored and the env contract, see [`../security/credential-storage.md`](../security/credential-storage.md).
@@ -1,95 +0,0 @@
1
- # OAuth Device Flows
2
-
3
- > Category: Auth | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- How `rflectr` signs into providers that use OAuth instead of an API key — OpenAI (ChatGPT), xAI (Grok), and GitHub Copilot — and how those tokens are stored and refreshed. Read [`../data/provider-registry.md`](../data/provider-registry.md) for how an OAuth provider is registered.
6
-
7
- **Related:**
8
- - [`../security/credential-storage.md`](../security/credential-storage.md)
9
- - [`../data/provider-registry.md`](../data/provider-registry.md)
10
- - [`../ai/translation-layer.md`](../ai/translation-layer.md)
11
- - Source: `src/oauth/` (`types.ts`, `openai.ts`, `xai.ts`, `github.ts`, `refresh.ts`, `pkce.ts`)
12
-
13
- ---
14
-
15
- ## Why device flow
16
-
17
- These hosts run in a terminal with no browser redirect URI to catch. The OAuth 2.0 **Device Authorization Grant** (RFC 8628) fits: `rflectr` asks the provider for a user code, prints a URL for the user to visit, then polls until the user approves. No localhost callback server, no redirect handling.
18
-
19
- `supportsNativeOAuth(providerId)` (`src/oauth/types.ts`) gates which providers offer this. The native set is `'xai' | 'xai-oauth' | 'openai' | 'openai-oauth' | 'github-copilot'`. The `rflectr providers auth <id>` command (`--native` vs `--broker`) drives it.
20
-
21
- ```mermaid
22
- sequenceDiagram
23
- participant U as User
24
- participant R as rflectr
25
- participant P as Provider auth server
26
- R->>P: request device/user code
27
- P-->>R: user_code + verification URL + interval
28
- R->>U: "visit <url>, enter <user_code>"
29
- loop until approved (respect interval / slow_down)
30
- R->>P: poll token endpoint
31
- P-->>R: authorization_pending | slow_down | tokens
32
- end
33
- P-->>R: access + refresh tokens
34
- R->>R: store StoredOAuthCredential in OS keyring
35
- ```
36
-
37
- ---
38
-
39
- ## Stored credential shape
40
-
41
- `StoredOAuthCredential` (`src/oauth/types.ts`) is `{ type: 'oauth', access, refresh, expires, accountId? }`. It is serialized to JSON and written to the keyring account `oauth:provider:<id>` (see [`../security/credential-storage.md`](../security/credential-storage.md)). Helpers:
42
-
43
- - `tokensToStoredCredential(tokens, existingRefresh?, accountId?)` — build it from a token response, preserving the existing refresh token if the provider didn't return a new one.
44
- - `parseStoredOAuthCredential(raw)` — parse + validate from keyring.
45
- - `oauthCredentialNeedsRefresh(cred, skewMs?)` — true when `expires <= now + skew` (default 120 s).
46
- - `accessTokenIsExpiring(token, skewMs?)` — decode the JWT `exp` and check proactively.
47
-
48
- ---
49
-
50
- ## The three providers
51
-
52
- ### OpenAI / ChatGPT (`src/oauth/openai.ts`)
53
-
54
- - Client id `app_EMoaamEEZ73f0CkXaXp7hrann`, issuer `https://auth.openai.com`.
55
- - `runOpenAiDeviceCodeFlow(onDeviceCode, opts?)`: POST `/api/accounts/deviceauth/usercode` → poll `/api/accounts/deviceauth/token` → exchange the authorization code (with PKCE `code_verifier`) at `/oauth/token`.
56
- - `extractOpenAiAccountId(tokens)` decodes the `id_token` / access token JWT for `chatgpt_account_id`. That `accountId` is what `provider-factory.ts` uses to route to the ChatGPT Codex backend (`https://chatgpt.com/backend-api/codex`).
57
- - `refreshOpenAiAccessToken(refreshToken)` — `grant_type=refresh_token` at `/oauth/token`.
58
-
59
- ### xAI / Grok (`src/oauth/xai.ts`)
60
-
61
- - Client id `b1a00492-073a-47ea-816f-4c329264a828`; device endpoint `https://auth.x.ai/oauth2/device/code`, token `https://auth.x.ai/oauth2/token`.
62
- - Scope `openid profile email offline_access grok-cli:access api:access`.
63
- - `requestXaiDeviceCode()` → `pollXaiDeviceCodeToken(device, opts?)` (grant `urn:ietf:params:oauth:grant-type:device_code`), combined as `runXaiDeviceCodeFlow`. Handles `slow_down` by bumping the interval +5 s.
64
- - `refreshXaiAccessToken(refreshToken)`.
65
-
66
- ### GitHub Copilot (`src/oauth/github.ts`)
67
-
68
- A two-step exchange — this is the quirk to remember:
69
-
70
- 1. Standard GitHub device flow (client id `Iv1.b507a08c87ecfe98`, the VS Code Copilot extension id; scope `copilot`) yields a long-lived `ghu_` access token.
71
- 2. `exchangeForCopilotToken(ghuToken)` GETs `https://api.github.com/copilot_internal/v2/token` to mint a **short-lived Copilot session token**.
72
-
73
- `refreshGithubCopilotToken(ghuToken)` simply re-exchanges the `ghu_` for a fresh session token — so the **stored `refresh` field is the `ghu_` itself**, not a conventional refresh token.
74
-
75
- > Caveat: Copilot OAuth *login* works, but Copilot as a *model provider* does not — OpenCode loads `@ai-sdk/github-copilot` from internal `@opencode-ai/core`, not a public npm factory. See [`../data/provider-registry.md`](../data/provider-registry.md).
76
-
77
- ---
78
-
79
- ## Refresh orchestration
80
-
81
- `src/oauth/refresh.ts` is the single dispatch point, called lazily at credential-resolution time (`resolveProviderCredential` in `src/env.ts`):
82
-
83
- - `oauthCredentialShouldRefresh(cred, providerId)` — true if `oauthCredentialNeedsRefresh(cred)` (time-based) **or**, for native providers, `accessTokenIsExpiring(cred.access)` (proactive JWT check).
84
- - `refreshStoredOAuthCredential(providerId, cred)` — routes to `refreshOpenAiAccessToken` / `refreshXaiAccessToken` / `refreshGithubCopilotToken` and returns a new `StoredOAuthCredential` via `tokensToStoredCredential`.
85
-
86
- Refresh is deduplicated per keyring account (`refreshOAuthKeyringAccount` in `src/env.ts`) so concurrent launches don't double-refresh, and a refreshed token is written back to the keyring. If refresh fails but the existing access token hasn't yet expired, the stale-but-valid token is used.
87
-
88
- ---
89
-
90
- ## PKCE & utilities (`src/oauth/pkce.ts`)
91
-
92
- - `generatePkce()` → `{ verifier, challenge }` with `challenge = SHA256(verifier)`.
93
- - `generateOAuthState()` → base64url of 32 random bytes.
94
- - `generateRandomString(length)` — crypto-random from the unreserved-char set.
95
- - `positiveSecondsToMs(value, defaultMs)`, `sleepMs(ms)` — polling helpers.
@@ -1,8 +0,0 @@
1
- # Data
2
-
3
- On-disk state: the provider registry and user preferences.
4
-
5
- | Doc | Covers |
6
- |---|---|
7
- | [`provider-registry.md`](provider-registry.md) | `~/.rflectr/providers.json` schema, built-in templates, registry → runtime materialization, the `providers` command. |
8
- | [`preferences-config.md`](preferences-config.md) | `~/.rflectr/config.json`, app-home resolution (`RFLECTR_HOME`), legacy-path migration, large-catalog UX constants. |
@@ -1,87 +0,0 @@
1
- # Preferences & Config Store
2
-
3
- > Category: Data | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- Where `rflectr` keeps user preferences, how the app-home directory is resolved, and the legacy-path migration. Read [`provider-registry.md`](provider-registry.md) for the separate providers file.
6
-
7
- **Related:**
8
- - [`provider-registry.md`](provider-registry.md)
9
- - [`../security/credential-storage.md`](../security/credential-storage.md)
10
- - Source: `src/paths.ts`, `src/config.ts`, `src/types.ts`
11
-
12
- ---
13
-
14
- ## The app home
15
-
16
- Everything `rflectr` writes lives under one directory, resolved by `getAppHome()` (`src/paths.ts`):
17
-
18
- 1. `RFLECTR_HOME` (or deprecated `OPENCODE_STARTER_HOME`) if set — overrides everything.
19
- 2. otherwise `~/.rflectr`.
20
-
21
- Files within it:
22
-
23
- | Path helper | File | Contents |
24
- |---|---|---|
25
- | `getConfigPath()` | `~/.rflectr/config.json` | User preferences (this doc) |
26
- | `getProvidersPath()` | `~/.rflectr/providers.json` | Provider registry ([`provider-registry.md`](provider-registry.md)) |
27
- | `getLogsPath()` | `~/.rflectr/logs/` | `--trace` debug logs |
28
- | `getVertexModelsPath()` | `~/.rflectr/vertex-models.json` | Optional Vertex model catalog |
29
-
30
- The directory is created with mode `0o700` and files with `0o600`. All writes are skipped when `dryRun === true`.
31
-
32
- ---
33
-
34
- ## Preferences schema
35
-
36
- `UserPreferences` (`src/types.ts`), read/written by `loadPreferences()` / `savePreferences()` (`src/config.ts`):
37
-
38
- | Field | Purpose |
39
- |---|---|
40
- | `lastBackend` | Last cloud backend (`zen` / `go`) |
41
- | `lastModel`, `lastProvider` | Last Claude Code selection (pre-selected in the wizard) |
42
- | `lastCodexProvider`, `lastCodexModel` | Last Codex selection |
43
- | `lastGeminiProvider`, `lastGeminiModel` | Last Gemini selection |
44
- | `recentModelsByProvider` | `Record<string, string[]>` — up to 3 recent model ids per provider |
45
- | `favoriteModels` | `FavoriteModel[]` (`{ providerId, modelId }`), max `MAX_MODEL_CATALOG` (20) |
46
- | `server` | Saved `server`-command settings |
47
-
48
- `savePreferences()` does a shallow merge — pass only the keys you're changing. `recordLaunchSelection(agent, providerId, modelId, prefs)` updates the per-agent `last*` fields and prepends to `recentModelsByProvider` (deduped, capped at 3).
49
-
50
- ```mermaid
51
- flowchart TD
52
- launch["launch a model"] --> rec["recordLaunchSelection()"]
53
- rec --> last["set last{Provider,Model} for the agent"]
54
- rec --> recent["prepend to recentModelsByProvider[provider] (max 3, dedup)"]
55
- last --> save["savePreferences() — skipped on --dry-run"]
56
- recent --> save
57
- ```
58
-
59
- `recentModelsByProvider` powers the "recent" hint at the top of `pickLocalModel` (`src/prompts.ts`), with a "Browse all models →" escape hatch.
60
-
61
- ---
62
-
63
- ## Legacy migration
64
-
65
- `rflectr` was previously `opencode-starter`, and its config moved twice. `readConfig()` (`src/config.ts`) migrates on first read, transparently:
66
-
67
- ```mermaid
68
- flowchart TD
69
- read["readConfig()"] --> homeMig["ensureAppHomeMigrated(): copy ~/.opencode-starter/config.json → ~/.rflectr/config.json"]
70
- homeMig --> confMig["ensureConfigMigrated(): copy OS-conf-store legacy config → ~/.rflectr/config.json"]
71
- confMig --> load["readJsonFile(getConfigPath()) ?? {}"]
72
- ```
73
-
74
- - `getLegacyAppHome()` → `~/.opencode-starter` (the prior dotfile dir). `vertex-models.json` migrates alongside `config.json`.
75
- - `getLegacyConfPath()` → the even older OS-specific `conf` location (macOS `~/Library/Preferences/opencode-starter-nodejs/`, Windows `%APPDATA%\opencode-starter-nodejs\Config\`, Linux `$XDG_CONFIG_HOME/opencode-starter-nodejs/`). After copying, the legacy file is renamed `…​.migrated` (best-effort).
76
- - `loadPreferences()` also normalizes the old `lastProvider === 'opencode'` value to `'zen'`.
77
-
78
- Migration is one-directional and idempotent: if `~/.rflectr/config.json` already exists, nothing is copied.
79
-
80
- ---
81
-
82
- ## Large-catalog UX constants
83
-
84
- Two constants in `src/prompts.ts` shape the picker once a provider's model list grows:
85
-
86
- - `MODEL_SEARCH_THRESHOLD = 25` — lists above this show search or paginated browse instead of a flat list.
87
- - `MODEL_PAGE_SIZE = 15` — page size for prev/next browsing (`selectModelWithSearch`, `selectLargeCatalog`, `pickModelFromPagedList`).
@@ -1,126 +0,0 @@
1
- # The Provider Registry
2
-
3
- > Category: Data | Version: 1.0 | Date: June 2026 | Status: Active
4
-
5
- The on-disk catalog of providers and their models — the single source of truth for every wizard. This doc covers the `~/.rflectr/providers.json` schema, the built-in templates, and how a registry entry becomes a runtime provider. Read [`../architecture/system-overview.md`](../architecture/system-overview.md) first.
6
-
7
- **Related:**
8
- - [`preferences-config.md`](preferences-config.md)
9
- - [`../ai/model-discovery-classification.md`](../ai/model-discovery-classification.md)
10
- - [`../security/credential-storage.md`](../security/credential-storage.md)
11
- - [`../auth/oauth-device-flows.md`](../auth/oauth-device-flows.md)
12
- - Source: `src/registry/` (`types.ts`, `io.ts`, `load.ts`, `materialize.ts`, `crud.ts`, `add-template.ts`, `import-opencode.ts`), `src/provider-templates.ts`, `src/providers-command.ts`, `src/provider-catalog.ts`, `src/paths.ts`
13
-
14
- ---
15
-
16
- ## Why a registry
17
-
18
- Early versions discovered providers by spawning OpenCode's `opencode serve` and reading `/config/providers` on every launch. That is slow and couples `rflectr` to a running OpenCode. The registry replaces that: providers are imported or added **once**, persisted to `~/.rflectr/providers.json`, and read directly on every launch. OpenCode import is now a one-time operation (`rflectr providers import`), not a per-launch dependency. OpenCode Zen / Go remain always available even with an empty registry.
19
-
20
- ---
21
-
22
- ## On-disk schema
23
-
24
- Path: `getProvidersPath()` → `~/.rflectr/providers.json` (`src/paths.ts`). Types in `src/registry/types.ts`:
25
-
26
- ```ts
27
- ProviderRegistry {
28
- schemaVersion: number // currently 1
29
- providers: RegistryProvider[]
30
- importedAt?: string
31
- pricingCacheAt?: string
32
- }
33
-
34
- RegistryProvider {
35
- id: string // /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/ (PROVIDER_ID_PATTERN)
36
- templateId: string // origin template, e.g. 'groq', 'custom-lm-studio'
37
- name: string // display name
38
- enabled: boolean
39
- authRef: string // 'keyring:provider:groq' | 'keyring:global:opencode' | 'env:OPENCODE_API_KEY'
40
- authType?: 'api' | 'oauth' | 'none'
41
- subscriptionFilter?: 'free' | 'zen' | 'go'
42
- api: { npm?: string; url?: string; id?: string }
43
- modelsCache?: { fetchedAt: string; models: CachedModel[] }
44
- addedAt: string // ISO
45
- refreshedAt?: string
46
- }
47
-
48
- CachedModel {
49
- id: string
50
- name: string
51
- upstreamModelId: string // provider's native id for the wire call
52
- family?: string; brand?: string
53
- contextWindow?: number
54
- cost?: { input: number; output: number }
55
- modelFormat: 'anthropic' | 'openai'
56
- npm?: string // per-model override of provider.api.npm
57
- apiUrl?: string // per-model override of provider.api.url
58
- sourceBackend?: string
59
- supportedParameters?: string[]
60
- reasoning?: boolean
61
- interleavedReasoningField?: string
62
- }
63
- ```
64
-
65
- **Persistence** (`src/registry/io.ts`): `loadRegistry(path?)` returns an empty registry if the file is missing/unparseable and applies migrations; `saveRegistry()` writes atomically (tmp + rename, backup) with secure permissions (`0o600` file inside a `0o700` dir via `ensureSecureAppHome()`), 2-space JSON + trailing newline. **No secrets live in this file** — `authRef` is only a pointer; the actual key lives in the OS keyring (see [`../security/credential-storage.md`](../security/credential-storage.md)).
66
-
67
- ---
68
-
69
- ## From registry entry to runtime provider
70
-
71
- ```mermaid
72
- flowchart TD
73
- file["~/.rflectr/providers.json"] --> load["loadRegistry()"]
74
- load --> lp["loadRegistryProviders() (async)"]
75
- lp --> cred["resolveProviderCredential(id, authRef) — env → keyring → OAuth refresh"]
76
- cred --> mat["materializeRegistry(registry, credResolver)"]
77
- mat --> filter["skip disabled / no-models / no-credential; shouldHideModel() per agent"]
78
- filter --> runtime["LocalProvider[] (the wizard list)"]
79
- ```
80
-
81
- - `loadRegistryProviders(diag?, opts?)` (`src/registry/load.ts`) reads enabled entries and resolves each credential.
82
- - `materializeRegistry(registry, credResolver, opts?)` (`src/registry/materialize.ts`) converts `CachedModel` → `LocalProviderModel`, drops providers with no credential or no cached models, and applies per-agent model hiding (`shouldHideModel()`) — e.g. Zen/Go favorites hidden from Codex.
83
- - `providersForPicker()` / `resolveLocalProviderApiKey()` / `formatRegistryAuthLabel()` (`src/provider-catalog.ts`) adapt the materialized providers for the interactive pickers and resolve the launch key.
84
-
85
- ---
86
-
87
- ## Built-in templates
88
-
89
- `src/provider-templates.ts` defines `ProviderTemplate`s — the menu of providers a user can add without hand-entering a base URL. Each carries `id`, `name`, `authType`, `npm` (the SDK package), `defaultBaseUrl?`, `signupUrl?`, `urlPrompt?` (for local servers), `modelSource` (`api-list | static-seed | manual-only | zen-go-api`), and `supported` / `addable` / `unsupportedReason` flags.
90
-
91
- | Group | Templates |
92
- |---|---|
93
- | API-key, addable | groq, mistral, togetherai, cerebras, deepinfra, deepseek, zhipu, moonshot(-global), kimi-code, xai, perplexity, cohere, openai, google, alibaba, openrouter, venice, anthropic |
94
- | Local servers | ollama, lmstudio (`urlPrompt`, `apiKeyOptional`) |
95
- | OAuth | github-copilot, xai |
96
- | Reference only (not addable) | bedrock (AWS creds), azure (deployment URLs), vertex (gcloud ADC) — carry `unsupportedReason` |
97
- | Cloud stubs | `zen`, `go` (`addable: false`); `opencode-cloud` routes to them. Models fetched live. |
98
-
99
- Query helpers: `listSupportedTemplates()`, `listAddableTemplates(configuredIds)`, `getTemplateById(id)`, `filterTemplates(templates, query)`.
100
-
101
- OpenCode is the source of truth for *which* models a provider exposes — `rflectr` does not maintain a per-package allowlist beyond these templates.
102
-
103
- ---
104
-
105
- ## The `providers` command
106
-
107
- `src/providers-command.ts` (`parseProvidersArgs` → `runProvidersCommand`):
108
-
109
- | Command | Function | Purpose |
110
- |---|---|---|
111
- | `rflectr providers` | `runProvidersHub()` | Interactive hub |
112
- | `… add` | `runProvidersAdd()` | Add from template or custom endpoint → `addProviderFromTemplate` |
113
- | `… import` | `runProvidersImport()` | One-time import from OpenCode → `importFromOpencode` |
114
- | `… list` | `runProvidersList()` | Tabular view |
115
- | `… remove <id>` | `runProvidersRemove(id)` | Remove entry + keyring cleanup → `removeProviderFromRegistry` |
116
- | `… refresh-models [id]` | `runProvidersRefreshModels(id?)` | Re-fetch the model cache |
117
- | `… auth <id> [--native\|--broker]` | `runProvidersAuth(id, method?)` | OAuth sign-in (see [`../auth/oauth-device-flows.md`](../auth/oauth-device-flows.md)) |
118
-
119
- CRUD lives in `src/registry/crud.ts` (`removeProviderFromRegistry`, `toggleProviderEnabled`), template-add in `src/registry/add-template.ts`, and OpenCode import in `src/registry/import-opencode.ts` (handles duplicate-provider migration).
120
-
121
- ---
122
-
123
- ## Unsupported-by-design providers
124
-
125
- - `@ai-sdk/github-copilot` won't work as a *model* provider — OpenCode loads it from internal `@opencode-ai/core`, not a public npm factory we can ship. (OAuth login still works; it's the model factory that's unavailable.)
126
- - Bedrock / Azure / Vertex may need env-based auth beyond a simple forwarded `apiKey`; they're reference-only templates. Vertex *is* supported through the dedicated `server --vertex` path (gcloud ADC) — see [`../infrastructure/server-gateway.md`](../infrastructure/server-gateway.md).
@@ -1,7 +0,0 @@
1
- # Infrastructure
2
-
3
- Long-lived services and runtime gateways.
4
-
5
- | Doc | Covers |
6
- |---|---|
7
- | [`server-gateway.md`](server-gateway.md) | `rflectr server` — the Anthropic/OpenAI-compatible gateway on port 17645, the model catalog + discovery-id masking, and Vertex AI mode (`--vertex`). |