All articles
platform engineering·intermediate·

One provider profile, six coding surfaces

Launchpad separates provider policy from tool configuration so six coding surfaces can share one model catalog and credential.

architectureclicoding-agentsdeveloper-experiencemodelssecurity
Resources
Resource Link
Source ravikanchikare/launchpad
Provider design reference OpenCode
LiteLLM proxy LiteLLM proxy documentation
OpenAI model catalog List models API
Claude Code settings Claude Code settings documentation

A shared model provider does not create a shared configuration format. Claude Code reads Anthropic environment variables, Codex accepts provider definitions, ChatGPT persists a profile, and Claude Desktop expects fixed model slots. Pointing every tool at one URL leaves the difficult part unresolved.

Launchpad makes the provider profile the stable boundary. Model discovery, endpoint resolution, and credential selection happen once. Six application surfaces then receive the narrow configuration each one understands.

Implementation snapshot
Concern Current implementation
Provider kinds litellm, openai-compatible
Credential sources LAUNCHPAD_PROVIDER_API_KEY, then macOS Keychain
Provider overrides URL, kind, and optional models URL
Terminal surfaces Claude Code, Codex, OpenCode, Copilot CLI
Desktop surfaces ChatGPT, Claude Desktop
Model selection Live catalog with keyboard filtering and navigation
Persistent recovery ChatGPT profile capture and restore

A provider profile owns endpoint rules

The first implementation treated the provider URL as an origin and appended paths inside each launcher. That rule breaks as soon as a URL already ends in /v1 or includes a tenant prefix. The same configured value can produce /v1/v1/models in one adapter and lose the prefix in another.

The replacement introduces one provider profile with three inputs: provider kind, base URL, and an optional models URL. The profile validates them and derives the OpenAI base, Anthropic base, and discovery endpoints.

This follows the provider seam used by OpenCode: generic settings remain separate from provider-specific discovery behavior. A launcher does not need to know where a LiteLLM catalog lives. It asks the profile for a resolved endpoint.


Discovery follows provider kind

LiteLLM exposes richer model-group metadata outside the OpenAI API surface. A generic OpenAI-compatible provider usually exposes only the standard model list. Probing both indiscriminately turns an authentication failure into an ambiguous fallback.

Model discovery rules
Provider kind Primary catalog Fallback Response shape
litellm /model_group/info /v1/models for an unavailable management route LiteLLM, then OpenAI
openai-compatible /v1/models None OpenAI
Either with modelsUrl Exact configured URL None OpenAI

The explicit models URL is for deployments where inference and discovery use different hosts or prefixes. It is not a second provider base. Launch requests still use the OpenAI or Anthropic base derived from the provider URL.


Configuration has one precedence order

Launchpad owns its environment contract instead of borrowing LiteLLM-specific variable names. This keeps the configuration accurate when the provider is not LiteLLM.

terminalbash
export LAUNCHPAD_PROVIDER_API_KEY=...
export LAUNCHPAD_PROVIDER_URL=https://provider.example.com/v1
export LAUNCHPAD_PROVIDER_KIND=openai-compatible

launchpad config show
launchpad launch claude

The URL, kind, and models URL use environment values first, then saved settings. The key resolves from LAUNCHPAD_PROVIDER_API_KEY first and macOS Keychain second. No LITELLM_* compatibility aliases remain.

The same profile can be saved without environment variables:

terminalbash
launchpad config set-provider https://provider.example.com/v1 \
  --kind openai-compatible \
  --models-url https://catalog.example.com/models

The models URL is optional. Omitting it restores provider-specific discovery.


Each surface gets a narrow adapter

The provider profile ends where tool configuration begins. Adapters receive resolved endpoints, a model ID, and a credential. They do not rediscover models or append API versions.

Anthropic environment

Claude Code

Sets the Anthropic base, auth token, selected model, default model slots, and model discovery flag in the child process.

OpenAI Responses provider

Codex

Passes a temporary provider definition through -c arguments, selects the model with -m, and supplies the key only to the child.

Managed profile block

ChatGPT

Captures the existing profile, writes one delimited provider block and model catalog, then retains enough state for an exact restore.

In-memory provider configuration

OpenCode

Builds an OpenAI-compatible provider document and passes it through OPENCODE_CONFIG_CONTENT without changing the user's config file.

Provider environment

Copilot CLI

Maps the resolved OpenAI base, key, provider type, and selected model into Copilot's process environment.

Local compatibility service

Claude Desktop

Advertises Claude model slots locally, rewrites each selected slot to a provider model, and forwards Anthropic-compatible requests.

Model IDs pass through unchanged. The picker selects provider-model-id; Claude receives that value as its model, Codex receives it through -m, and OpenCode places it under the Launchpad provider namespace.


Credentials remain process-scoped

Saved settings contain provider kind, URL, and optional models URL. They do not contain the provider key. Terminal adapters construct a child environment from the current process, remove Launchpad’s provider variables, remove conflicting tool credentials, then add only the variables required by the selected integration.

ChatGPT and Claude Desktop need credentials after the initiating CLI process exits. Their flows persist the management key in macOS Keychain and configure a helper or local service instead of writing the key into general settings JSON.


Persistent changes require restoration

ChatGPT shares configuration with Codex under CODEX_HOME. Launchpad captures the existing root model fields, writes a delimited provider block, and records the generated model catalog before restarting the desktop app.

The restore command removes only Launchpad’s managed block, restores the captured values, removes the generated catalog, and asks before restarting ChatGPT:

terminalbash
launchpad launch chatgpt --restore

This is not cleanup around the implementation. It is part of the adapter contract: a persistent integration is incomplete until the previous state can be recovered.


Verify the boundary

The tests exercise provider URLs with and without /v1, path prefixes, LiteLLM fallback, direct OpenAI discovery, explicit catalog URLs, environment precedence, child credential isolation, Claude request rewriting, and ChatGPT restore.

terminalbash
go test -race -timeout 60s ./...
go vet ./...
npm --prefix ui run build

A smoke test can stop at picker cancellation. That proves credential resolution and live catalog discovery without starting a model request.


Takeaways

A provider is more than a URL

Provider kind, catalog endpoint, wire protocol, and credential source form one profile. Treating them as independent strings creates invalid combinations.

Endpoint derivation needs one owner

The provider profile handles path prefixes and /v1 exactly once. Tool adapters consume resolved OpenAI and Anthropic bases.

Discovery and execution stay separate

The catalog selects a model ID. Each adapter translates that selection into the tool's native process or profile contract.

Restore is part of configuration safety

Persistent profile changes need captured state, explicit confirmation, and a tested path back to the original configuration.