@jacobbd/relay-ai 0.2.0
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.
- package/.cursor/rules/post-change-build-link.mdc +19 -0
- package/AGENTS.md +168 -0
- package/CHANGELOG.md +85 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/assets/banner.png +0 -0
- package/dist/cli.js +12599 -0
- package/package.json +79 -0
- package/scripts/refresh-models-dev-cache.mjs +34 -0
- package/vertex-models.example.json +14 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: After relay-ai code changes, always build and npm link — Jacob does not run this manually
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Post-change build + link (relay-ai)
|
|
7
|
+
|
|
8
|
+
Jacob tests relay-ai via the **global npm-linked** CLI. He does **not** want to run build/link himself.
|
|
9
|
+
|
|
10
|
+
After **any** code change to this repo that affects runtime behavior (`src/`, `package.json`, dependencies, etc.):
|
|
11
|
+
|
|
12
|
+
1. Run **`npm run build`** — required before manual testing
|
|
13
|
+
2. Run **`npm link`** — refreshes the global `relay-ai` command to point at this workspace
|
|
14
|
+
|
|
15
|
+
Do this **automatically** at the end of the task. Do not tell Jacob to run it unless the command failed and needs his attention.
|
|
16
|
+
|
|
17
|
+
Skip only when the session was **read-only** (questions, review with no edits).
|
|
18
|
+
|
|
19
|
+
On success, a short note like “Built and linked — ready to test” is enough.
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run build # compile TypeScript → dist/cli.js (via tsup, ESM, shebang injected)
|
|
9
|
+
npm test # run all tests with vitest
|
|
10
|
+
npm run typecheck # type-check without emitting (tsc --noEmit)
|
|
11
|
+
npm run dev # watch mode build
|
|
12
|
+
|
|
13
|
+
# Run a single test file
|
|
14
|
+
npx vitest run tests/env.test.ts
|
|
15
|
+
npx vitest run tests/models.test.ts
|
|
16
|
+
|
|
17
|
+
# Test the CLI locally (already npm-linked)
|
|
18
|
+
relay-ai --help
|
|
19
|
+
relay-ai models # manage favorite models for mid-session switching
|
|
20
|
+
relay-ai Codex --dry-run # simulate full first-run without writing anything
|
|
21
|
+
relay-ai Codex --setup # re-ask subscription tier
|
|
22
|
+
relay-ai Codex --trace # write debug log to /tmp/relay-ai-debug.log and print errors on exit
|
|
23
|
+
relay-ai server # foreground OpenCode/registry API gateway
|
|
24
|
+
relay-ai server --vertex # foreground Vertex AI gateway (gcloud ADC)
|
|
25
|
+
relay-ai codex # Codex CLI with registry providers (see docs/CODEX.md)
|
|
26
|
+
relay-ai codex-app # Codex desktop app (macOS/Windows; see docs/CODEX.md)
|
|
27
|
+
|
|
28
|
+
# Rebuild after code changes before testing manually
|
|
29
|
+
npm run build && relay-ai --version
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
**Entry point:** `src/cli.ts` orchestrates the full flow. Every other module is a focused unit with no side effects at import time.
|
|
35
|
+
|
|
36
|
+
**Data flow (`relay-ai Codex`):**
|
|
37
|
+
```
|
|
38
|
+
cli.ts
|
|
39
|
+
→ findClaudeBinary() [launch.ts — locate Codex binary]
|
|
40
|
+
→ fetchLocalProviders() [providers.ts — ephemeral opencode serve, GET /config/providers, normalize]
|
|
41
|
+
→ p.select "Which provider?" [shown when local providers are available]
|
|
42
|
+
|
|
43
|
+
── OpenCode cloud path (default) ──
|
|
44
|
+
→ resolveOrCollectApiKey() [reads env, OS credential store (all platforms), or prompts user]
|
|
45
|
+
→ askSubscriptionTier() [prompts.ts — one-time question, saved to conf store]
|
|
46
|
+
→ getModels() [models.ts — API fetch + cache enrichment + format classification]
|
|
47
|
+
→ runWizard() [prompts.ts — backend/model selector, filters unsupported]
|
|
48
|
+
|
|
49
|
+
── Local provider path ──
|
|
50
|
+
→ pickLocalModel() [prompts.ts — filter/select model from local provider]
|
|
51
|
+
|
|
52
|
+
── Shared launch (no favorites) ──
|
|
53
|
+
→ startProxy() [proxy.ts — single-model wrapper around startProxyCatalog]
|
|
54
|
+
→ buildChildEnv(baseUrl, …) [env.ts — removes 17 conflicting vars, sets OpenCode vars]
|
|
55
|
+
→ launchClaude() [launch.ts — spawn with stdio:inherit]
|
|
56
|
+
→ proxyHandle.close() [stops proxy after Codex exits]
|
|
57
|
+
|
|
58
|
+
── Switch-menu launch (favorites.length > 0) ──
|
|
59
|
+
→ buildCatalogRoutes() [catalog.ts — starting model + favorites, max 20]
|
|
60
|
+
→ startProxyCatalog() [proxy.ts — multi-route proxy, alias IDs per model]
|
|
61
|
+
→ buildChildEnv(…, gatewayDiscovery=true) [sets CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1]
|
|
62
|
+
→ launchClaudeViaCatalog() [cli.ts — shared launch + trace cleanup]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**`relay-ai models`:** Interactive favorites manager (`src/favorites.ts`). Reads/writes `favoriteModels` in config. Saves once on Done. Stale favorites (unavailable models) are silently skipped when building the catalog.
|
|
66
|
+
|
|
67
|
+
**Catalog routing** (`src/catalog.ts`): `localModelToRoute`, `zenGoModelToRoute`, `makeRouteResolver`, `buildCatalogRoutes`. Routes built only for starting model + favorites — not the full model list. Alias IDs via `aliasModelId()` in proxy so Codex sees unique model names in `/model`.
|
|
68
|
+
|
|
69
|
+
**Critical URL constraint:** `BACKENDS.baseUrl` in `constants.ts` must NOT include `/v1`. The Anthropic SDK appends `/v1/messages` automatically. Setting it to `https://opencode.ai/zen/v1` would cause requests to hit `/zen/v1/v1/messages` → 404.
|
|
70
|
+
|
|
71
|
+
**Model discovery two-source merge:**
|
|
72
|
+
- Primary: `GET {backendUrl}/v1/models` (no auth needed, returns available IDs)
|
|
73
|
+
- Enrichment: `~/.cache/opencode/models.json` (written by OpenCode CLI) — provides `name`, `family`, `cost`, `provider.npm`
|
|
74
|
+
- `isAnthropicNative`: true when `modelFormat === 'anthropic'`
|
|
75
|
+
- `modelFormat`: classified from `provider.npm` in cache, or by ID-prefix heuristic:
|
|
76
|
+
- `@ai-sdk/anthropic` or `Codex-*` → `'anthropic'` (direct passthrough)
|
|
77
|
+
- `@ai-sdk/openai` or `gpt-*` → `'unsupported'` in the **cloud OpenCode wizard** (OpenCode Zen/Go proxy layer; not direct OpenAI). Use the **local OpenAI provider** instead for GPT models.
|
|
78
|
+
- `@ai-sdk/google` or `gemini-*` → `'unsupported'` (needs model-specific endpoints)
|
|
79
|
+
- Everything else → `'openai'` (routed through the SDK adapter via the local proxy)
|
|
80
|
+
- `sourceBackend`: set from the backend that was queried — critical for `go` tier which shows Zen free models + Go paid models in one list, so the correct `ANTHROPIC_BASE_URL` can be set per selected model
|
|
81
|
+
|
|
82
|
+
**Translation layer — the Vercel AI SDK adapter** (`src/sdk-adapter.ts` + `src/provider-factory.ts`): All non-Anthropic providers route through the Vercel AI SDK (`ai` + `@ai-sdk/*`, the same packages OpenCode loads), which owns wire format, endpoint selection, and provider quirks. This is the **single** translation path — there is no hand-rolled per-provider translation.
|
|
83
|
+
|
|
84
|
+
- **`provider-factory.ts`** — `createLanguageModel({ npm, modelId, apiKey, baseURL })` (async) maps whatever `api.npm` OpenCode assigns to an SDK `LanguageModel` via dynamic `import(npm)` + `create*` factory discovery. Special branches for OpenAI/xAI Responses API selection and openai-compatible/openrouter base URLs. `isSdkMigratedNpm(npm)` is true for any npm except `@ai-sdk/anthropic`. `modelPrefersResponsesApi(modelId)` selects `provider.responses(id)` over `provider.chat(id)` for OpenAI/xAI models that require the Responses API (GPT-5.4+, GPT-5.5, `*-codex`, o-series, xAI `*-multi-agent`). OpenCode's bundled SDK provider packages ship as npm `dependencies` (externalized in tsup, loaded on demand).
|
|
85
|
+
- **`sdk-adapter.ts`** — Anthropic `/v1/messages` ↔ SDK, one turn per request (Codex owns the tool loop). `translateRequest(body, npm)` builds the SDK call params (messages, tools, tool_choice, system) and folds inline `role:'system'` messages — Codex injects the skills list / system-reminders this way — into the system prompt so they aren't dropped. `streamAnthropicResponse` maps the SDK `fullStream` to Anthropic SSE; `generateAnthropicResponse` handles non-streaming. `thought_signature` round-trips: encoded into the Anthropic `tool_use.id` as `{id}::ts::{signature}` and decoded back into `providerOptions.google.thoughtSignature` (Gemini puts the signature on the tool-call parts, captured at `tool-input-start`). The SDK handles Gemini's strict `thought_signature` echo-back correctly — the reason a hand-rolled Gemini-native path used to be required.
|
|
86
|
+
|
|
87
|
+
**Local proxy** (`src/proxy.ts`): a local HTTP server on `127.0.0.1:<random-port>` that accepts Anthropic-format requests at `/v1/messages` and dispatches per route (`startProxyCatalog`/`startProxy`): `modelFormat === 'anthropic'` → direct passthrough to the provider's Anthropic endpoint; otherwise → `isSdkMigratedNpm(route.npm)` → the SDK adapter. Each `ProxyRoute` carries `npm` + `baseURL`. `GET /v1/models` returns a synthetic catalog including `context_window` per model (via `formatAnthropicModelEntry` / `resolveContextWindow`) so Codex's status bar shows accurate remaining context. `aliasModelId()` rewrites non-`Codex-*` ids to `anthropic-{provider}__{id}` so gateway model discovery accepts them.
|
|
88
|
+
|
|
89
|
+
**Subscription tiers** control which models are shown and whether a backend selector appears:
|
|
90
|
+
- `free` / `zen`: always Zen backend, no backend selector
|
|
91
|
+
- `go`: Go backend, but also fetches Zen for free models — combined list, backend inferred from `sourceBackend` of selected model
|
|
92
|
+
- `both`: shows backend selector
|
|
93
|
+
|
|
94
|
+
**Env isolation:** `buildChildEnv()` copies `process.env`, deletes all 17 vars in `CONFLICTING_ENV_VARS`, then sets `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `ANTHROPIC_MODEL`. `launchClaude()` also passes `--model`. Isolation applies to the child process only — the parent shell is not mutated (except `OPENCODE_API_KEY` during key setup). Codex may persist the model to `~/.Codex/settings.json` independently; that is outside relay-ai's control.
|
|
95
|
+
|
|
96
|
+
**Preferences** (at `~/.relay-ai/config.json`, migrated from legacy `conf` path on first read): `lastBackend`, `lastModel`, `lastProvider`, `recentModelsByProvider`, `favoriteModels`, `subscriptionTier`, and a 1-hour model list cache. Override path with `RELAY_AI_HOME`. All writes are skipped when `dryRun === true`.
|
|
97
|
+
|
|
98
|
+
**API key storage** uses `@napi-rs/keyring` (installed as `optionalDependencies`) for cross-platform credential store access. The module is loaded via dynamic `import()` so a missing native binary degrades gracefully. `tsup.config.ts` marks `@napi-rs/keyring` and all `@ai-sdk/*` provider packages as `external` so they resolve from `node_modules` at runtime (keeps `dist/cli.js` small).
|
|
99
|
+
|
|
100
|
+
On startup, `resolveOrCollectApiKey()` silently calls `readFromCredentialStore()` — if a key is found the prompt is skipped entirely.
|
|
101
|
+
|
|
102
|
+
Save options per platform:
|
|
103
|
+
- **macOS** (4 options): Keychain only | Keychain + `~/.zshrc` auto-load | shell profile (plaintext) | session only
|
|
104
|
+
- The `~/.zshrc` auto-load line uses the `security` CLI directly (so the shell can source it): `export OPENCODE_API_KEY="$(security find-generic-password -s relay-ai -a relay-ai -w 2>/dev/null)"`
|
|
105
|
+
- **Windows** (3 options): Windows Credential Manager | `setx` user env var (plaintext) | session only
|
|
106
|
+
- `setx` is called with `stdio: ['pipe','pipe','pipe']` to suppress its "SUCCESS" stdout
|
|
107
|
+
- **Linux desktop** (3 options): Secret Service (GNOME Keyring / KWallet) | shell profile (plaintext) | session only
|
|
108
|
+
- Secret Service availability is probed via a test `getPassword()` call — returns false if the daemon isn't running
|
|
109
|
+
- **Linux headless** (2 options): shell profile | session only — shown with a `p.log.info` note explaining why secure storage is unavailable
|
|
110
|
+
|
|
111
|
+
In all cases `process.env['OPENCODE_API_KEY']` is set immediately so the key is active for the current session regardless of save choice.
|
|
112
|
+
|
|
113
|
+
**Local provider discovery** (`src/providers.ts`): `fetchLocalProviders()` spawns `opencode serve --port 0`, waits for the listening URL in stdout/stderr (10s timeout, spinner shown in CLI), fetches `GET /config/providers`, then kills the process. `normalizeProviders()` (called internally) skips OAuth providers (empty key), and classifies each model via `resolveEndpoint(npm, apiUrl)`: `@ai-sdk/anthropic` → passthrough; `@ai-sdk/openai-compatible` without `api.url` → skip; any other non-empty `api.npm` → SDK adapter (`format: 'openai'`). OpenCode is the source of truth for which providers/models appear — relay-ai does not maintain a per-package allowlist. Each model captures `api.npm`, `api.url` (`apiBaseUrl`), and `api.id` (`upstreamModelId` for SDK/upstream calls; catalog `id` stays for Codex's picker). Cost display in Codex is inaccurate for non-Anthropic models (Codex applies its own pricing table); documented limitation.
|
|
114
|
+
|
|
115
|
+
**Local provider routing:** Two paths depending on `model.modelFormat`:
|
|
116
|
+
- `'anthropic'`: `buildChildEnv(model.baseUrl, model.id, provider.apiKey)` — no proxy, Codex talks directly to the provider's Anthropic-compatible endpoint. The `baseUrl` must NOT include `/v1` (the Anthropic SDK appends it).
|
|
117
|
+
- `'openai'`: `startProxy(model.completionsUrl ?? '', model.id, trace, contextWindow, { npm, baseURL, upstreamModelId })` — SDK adapter proxy on a random local port; `buildChildEnv('http://127.0.0.1', model.id, provider.apiKey, proxyPort)`. The route's `npm` selects the SDK provider via dynamic import; `baseURL` (`api.url`) is used for openai-compatible / openrouter providers. `completionsUrl` is optional for SDK-first-party packages (SDK owns endpoints).
|
|
118
|
+
|
|
119
|
+
**Providers that need a non-empty API key:** `normalizeProviders` skips any provider with an empty `key` field (to filter OAuth-only providers like OpenAI/xAI configured via browser login). Local providers that don't validate keys (e.g. Ollama) must still have a non-empty placeholder key set in OpenCode (e.g. `"ollama"`).
|
|
120
|
+
|
|
121
|
+
**Server command local providers** (`src/server/index.ts`): `loadServerModels()` fetches the provider catalog via `fetchProviderCatalog({ agent: 'server' })` and converts all registry providers to `ServerModelInfo[]` via `localProvidersToServerModels`. The router (`src/server/router.ts`) `handleAnthropicMessages`: anthropic-format → forward raw to `{baseUrl}/v1/messages`; openai-format → `isSdkMigratedNpm(npm)` guard → `createLanguageModel` + `streamAnthropicResponse`/`generateAnthropicResponse` (same SDK adapter as the CLI proxy). `GET /models` strips `apiKey` from output. Spinner shows `"N models (M from registry providers)"`.
|
|
122
|
+
|
|
123
|
+
**Stale free models:** `STALE_FREE_MODELS` in `constants.ts` contains models whose free promotion ended but the API still returns them. Currently only `qwen3.6-plus-free`. These are filtered out in `mergeModels()`.
|
|
124
|
+
|
|
125
|
+
**Recent models per provider** (`src/prompts.ts`, `src/cli.ts`, `src/types.ts`, `src/config.ts`): `UserPreferences.recentModelsByProvider: Record<string, string[]>` stores up to 3 recently used model IDs per provider. `pickLocalModel()` shows them at the top of the picker with a `'recent'` hint, plus a "Browse all models →" option. On launch, `cli.ts` prepends the selected model id and saves back (deduped, max 3). Skipped on `--dry-run`.
|
|
126
|
+
|
|
127
|
+
**Large catalog UX** (`src/prompts.ts`): `MODEL_SEARCH_THRESHOLD = 25` — lists above this show search or paginated browse. `MODEL_PAGE_SIZE = 15` — prev/next pagination. `selectModelWithSearch`, `selectLargeCatalog`, `pickModelFromPagedList`.
|
|
128
|
+
|
|
129
|
+
**Shared upstream forwarding** (`src/upstream-forward.ts`): `relayAnthropicMessages`, `postJsonUpstream`, anthropic header helpers — used by `proxy.ts` and `server/router.ts`.
|
|
130
|
+
|
|
131
|
+
**Provider catalog helpers** (`src/provider-catalog.ts`): `fetchProviderCatalog`, `resolveLocalProviders`, `providersForPicker`, `localProvidersToServerModels`, `resolveProvidersForDisplay`, `formatRegistryAuthLabel` — registry-first catalog resolution used by CLI, server, and providers command.
|
|
132
|
+
|
|
133
|
+
**Tests** cover pure functions: `env.ts`, `models.ts`, `sdk-adapter.ts`, `provider-factory.ts`, `proxy.ts` (`aliasModelId`), `providers.ts`, `catalog.ts`, `favorites.ts`, `prompts.ts`, `upstream-forward.ts`, `config.ts`, `tool-search.ts`, `cli.ts` (help text), server modules. Interactive launch flow and real-provider behavior verified manually.
|
|
134
|
+
|
|
135
|
+
## Key constraints
|
|
136
|
+
|
|
137
|
+
- `settings.json` is never touched by relay-ai. Launch config is env-var-only, passed to the child process (plus `--model`). This avoids the backup/restore problem that `ollama launch Codex` has. **Caveat:** Codex itself persists the launched model to `~/.Codex/settings.json`, so bare `Codex` later may still show an relay-ai alias (e.g. `anthropic-opencode-go__deepseek-v4-flash`). Gateway discovery caches at `~/.Codex/cache/gateway-models.json`. Reset with `Codex --model sonnet` or by editing/removing those files.
|
|
138
|
+
- `--dry-run` ignores all saved state (env key, Keychain, tier, preferences) and skips all writes. Used to simulate a fresh first-run experience.
|
|
139
|
+
- When adding a new backend, update `BACKENDS` in `constants.ts`, the `BackendConfig` id union in `types.ts`, and the subscription tier logic in `prompts.ts` and `cli.ts`.
|
|
140
|
+
- `buildChildEnv(baseUrl: string, model, apiKey, proxyPort?)` — takes a plain string URL, not a `BackendConfig`. When `proxyPort` is set, `ANTHROPIC_BASE_URL` is always `http://127.0.0.1:{proxyPort}` regardless of `baseUrl`.
|
|
141
|
+
- `startProxy(completionsUrl, modelId, debug, contextWindow?, sdk?)` — single-model wrapper around `startProxyCatalog`; `sdk` carries `{ npm, baseURL }` to select the SDK provider.
|
|
142
|
+
- `startProxyCatalog(routes, startingAliasId, debug)` — multi-route catalog proxy for switch-menu sessions.
|
|
143
|
+
- `MAX_MODEL_CATALOG = 20` in `constants.ts` — favorites cap and max routes in catalog.
|
|
144
|
+
|
|
145
|
+
**Codex favorites catalog:** When `prefs.favoriteModels.length > 0`, `relay-ai codex` and `relay-ai codex-app` enter favorites mode on launch:
|
|
146
|
+
- Shared resolver (`src/favorites-resolver.ts`) resolves each favorite to a `{providerId, providerName, model, apiKey}` entry, filtering by `agent: 'codex'` blacklist.
|
|
147
|
+
- Codex CLI builds a `CodexProxyRoute[]` from resolved entries and starts a single multi-route proxy (`startCodexProxy(routes, { requireAuth: true })`).
|
|
148
|
+
- The proxy port is exposed to the child via `OPENAI_API_KEY=proxy-local`.
|
|
149
|
+
- Catalog slugs are `${providerId}__${modelId}` (CLI) or `codexAppModelSlug(modelId)` (App).
|
|
150
|
+
- `--restore` globs `models-*.json` (CLI) and `app-models-*.json` (App); the new files are `models-favorites.json` and `app-models-favorites.json`.
|
|
151
|
+
- Zen/Go favorites are skipped in Codex (use Claude or Desktop gateway).
|
|
152
|
+
|
|
153
|
+
## Release status (v0.2.0)
|
|
154
|
+
|
|
155
|
+
Current version is **v0.2.0** — native provider registry (`relay-ai providers`), registry-first launch, import key validation, and Phase 1.3 dead-code cleanup. Prior work includes the SDK adapter from the pre-0.2 lineage (see CHANGELOG `[0.3.0]`).
|
|
156
|
+
|
|
157
|
+
**Known limitations (by design):**
|
|
158
|
+
- Cost display in Codex is always inaccurate for non-Anthropic models.
|
|
159
|
+
- OAuth-authenticated providers (no stored key) are silently skipped.
|
|
160
|
+
- `@ai-sdk/github-copilot` won't work — OpenCode loads it from internal `@opencode-ai/core`, not a public npm factory we can ship.
|
|
161
|
+
- Bedrock/Azure/Vertex may need env-based auth beyond a simple `apiKey` forwarded from OpenCode.
|
|
162
|
+
- Providers with custom auth mechanisms (e.g. Azure OpenAI with deployment URLs) are not fully supported.
|
|
163
|
+
- The `::ts::` separator in tool_use ids encodes `thought_signature`; would break if a signature ever literally contained `::ts::`. Extremely unlikely.
|
|
164
|
+
- In switch-menu (gateway-discovery) mode the displayed context window reflects the **launch** model and does NOT update on live `/model` switch. Codex'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) is the only lever. Single-model launches show the correct window.
|
|
165
|
+
|
|
166
|
+
**Provider quirks (documented from testing):**
|
|
167
|
+
- **Mistral free tier:** strict API rate limits (HTTP 429, code `1300`). Tool-heavy Codex sessions burn quota quickly (parallel title-generation requests, Skill injection, multi-turn tool loops). The SDK handles Mistral message ordering; throttling is unaffected.
|
|
168
|
+
- **OpenAI direct (`@ai-sdk/openai` local provider):** newer models (GPT-5.4+, GPT-5.5, `*-codex`, o-series) require the Responses API — `provider-factory.modelPrefersResponsesApi()` selects `openai.responses(id)` for them, `openai.chat(id)` otherwise. OpenCode catalog IDs may differ from upstream API IDs — `upstreamModelId` uses OpenCode's `api.id` (e.g. `gpt-5.5-fast` → `gpt-5.5`). GPT-5.5 reasoning round-trips via encrypted content in `thinking.signature`. Cloud OpenCode Zen/Go GPT models remain hidden in the wizard (`unsupported`); use the local OpenAI provider for GPT access.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Cross-surface favorites catalog** — Codex CLI (`relay-ai codex`) and Codex App (`relay-ai codex-app`) use `relay-ai models` favorites for mid-session model switching.
|
|
7
|
+
- Server wizard defaults to favorites-only catalog when favorites are configured.
|
|
8
|
+
- Provider labels in gateway model picker when using favorites-only mode.
|
|
9
|
+
|
|
10
|
+
## [0.2.0] - 2026-06-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Native provider registry** — `relay-ai providers` command (add, import, list, remove, refresh-models)
|
|
14
|
+
- Provider templates for 17+ SDK-backed providers and custom OpenAI/Anthropic endpoints
|
|
15
|
+
- First-run inline wizard when registry is empty
|
|
16
|
+
- Import key validation (rejects OpenCode placeholder keys; skips Vertex/Bedrock/Azure key storage)
|
|
17
|
+
- Async pricing enrich with bundled `src/data/pricing-cache.json`
|
|
18
|
+
- Secure trace logs under `~/.relay-ai/logs/`
|
|
19
|
+
- [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- **`relay-ai claude` / `models` / `server`** load from registry first — no `opencode serve` on launch
|
|
23
|
+
- Removed `subscriptionTier` and config model-list cache (Zen filter lives on registry provider)
|
|
24
|
+
- `--setup` now points users to `relay-ai providers`
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-06-09
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **Rebrand:** `opencode-starter` → **`relay-ai`**. CLI command, config dir (`~/.relay-ai/`), and env override (`RELAY_AI_HOME`) updated throughout.
|
|
30
|
+
- Migrates existing config from `~/.opencode-starter/` and legacy credential-store entries on first run.
|
|
31
|
+
|
|
32
|
+
## [0.3.0] - 2026-06-07
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Local OpenCode provider discovery — launch Claude Code with any provider configured in OpenCode: Groq, Mistral, xAI, Google/Gemini, OpenAI, Anthropic-direct, Ollama, OpenRouter, Cerebras, Perplexity, Bedrock, Vertex, and more. Includes full Gemini thinking + tool calls, OpenAI Responses-API models (GPT-5.4+, GPT-5.5, Codex, o-series), and Mistral.
|
|
36
|
+
- Bundled OpenCode SDK provider packages (Cerebras, Perplexity, Bedrock, Vertex, Azure, Together AI, DeepInfra, Alibaba, GitLab, Venice, and others) so any provider configured in OpenCode resolves at runtime without extra installs.
|
|
37
|
+
- `relay-ai models` — interactive favorites manager (up to 20 models) for mid-session switching. With favorites set, Claude Code's `/model` switches live between your starting model and your favorites.
|
|
38
|
+
- Model picker search and paginated browse for large catalogs; recent models per provider shown at the top of pickers.
|
|
39
|
+
- Accurate `context_window` in synthetic `/v1/models` responses so Claude Code's status bar shows real remaining context.
|
|
40
|
+
- `relay-ai server` exposes local-provider models with per-model routing; `GET /models` never returns API keys.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- All providers route through a single Vercel AI SDK adapter (`ai` + `@ai-sdk/*`), which owns wire format, endpoint selection, and provider quirks (Gemini `thought_signature`, xAI multi-agent `/responses`, Mistral message ordering). Both the `claude` launch proxy and the `server` command use it; Anthropic-format models remain direct passthrough.
|
|
44
|
+
- Local provider discovery trusts OpenCode's `api.npm` — removed the static SDK allowlist; any configured provider appears in the picker and routes through the SDK adapter (except `@ai-sdk/anthropic` passthrough and `@ai-sdk/openai-compatible` without a base URL).
|
|
45
|
+
- `createLanguageModel` is async and dynamically imports the `create*` factory from whatever npm package OpenCode assigns.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- Local OpenAI providers: use OpenCode's `api.id` as the upstream model ID so catalog aliases (e.g. `gpt-5.5-fast` → `gpt-5.5`) don't hit `model_not_found` on the OpenAI API.
|
|
49
|
+
- GPT-5.5 multi-turn reasoning: round-trip encrypted reasoning content via the Responses API without leaking SDK warnings into the Claude Code TUI.
|
|
50
|
+
|
|
51
|
+
### Docs
|
|
52
|
+
- Note that Claude Code persists launched models to `~/.claude/settings.json` and may cache gateway catalogs — bare `claude` can show relay-ai aliases after a session.
|
|
53
|
+
- Updated README and CLAUDE.md for SDK adapter proxy naming, OpenCode-trusted discovery, `upstreamModelId`, and provider compatibility.
|
|
54
|
+
|
|
55
|
+
## [0.2.5] - 2026-06-05
|
|
56
|
+
|
|
57
|
+
- Fix: Pass `X-API-Key` headers to upstream servers for paid Anthropic-format models.
|
|
58
|
+
- Fix: Blacklist deprecated MiMo models `mimo-v2-pro` and `mimo-v2-omni` to prevent runtime failures.
|
|
59
|
+
|
|
60
|
+
## [0.2.4] - 2026-06-05
|
|
61
|
+
|
|
62
|
+
- Fix: Server network mode now displays the actual local LAN IP address instead of the `<this-computer-ip>` placeholder.
|
|
63
|
+
- Fix: Server password prompt now shows input in plaintext so users can verify what they typed before confirming.
|
|
64
|
+
- Fix: Server network mode startup output now shows the actual server password so users can share/copy it.
|
|
65
|
+
|
|
66
|
+
## [0.2.3] - 2026-06-05
|
|
67
|
+
|
|
68
|
+
- Fix: The `relay-ai server` command now automatically resolves/loads the API key from the OS credential store (Keyring/Keychain/Credential Manager) if it's not exported in the shell environment.
|
|
69
|
+
- Docs: Added instructions to the `README.md` on how to upgrade the package.
|
|
70
|
+
|
|
71
|
+
## [0.2.2] - 2026-06-05
|
|
72
|
+
|
|
73
|
+
- Simplified dry-run key-save logging in `resolveOrCollectApiKey` — replaced a 7-branch `if-else` chain with a `Record<SaveChoice, string>` lookup table. No behavioral change.
|
|
74
|
+
|
|
75
|
+
## [0.2.1]
|
|
76
|
+
|
|
77
|
+
- Changed the Claude Code launch path to the `relay-ai claude` command namespace. Bare `relay-ai` now prints help and migration guidance instead of launching Claude Code.
|
|
78
|
+
- Preserved passthrough Claude Code args after `claude`, including `-c`, `--resume <session-id>`, session IDs, and args after `--`.
|
|
79
|
+
- Added foreground `relay-ai server` mode for local or LAN API gateway use.
|
|
80
|
+
- Added Anthropic-compatible and limited OpenAI-compatible server endpoints.
|
|
81
|
+
- Moved app preferences and model cache to `~/.relay-ai/config.json`.
|
|
82
|
+
- Added one-time migration from the previous OS-native config path.
|
|
83
|
+
- Added opt-in saved server password support for network server mode.
|
|
84
|
+
- Updated documentation with the supported tools command table, Claude examples, dry-run/setup/trace placement, and migration note.
|
|
85
|
+
- Added MIT license file.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacob Ben David
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|