Fixes#2903
## The Bug
Azure OpenAI users get `AI_LoadAPIKeyError: OpenAI API key is missing`
despite passing a properly configured `OpenAI` instance to
`OpenAIAdapter`, exactly as shown in the docs.
**Root cause**: `CopilotRuntime.handleServiceAdapter()` discards the
adapter's configured OpenAI instance and constructs a plain string
`"openai/gpt-4o"` to pass to `BuiltInAgent`. The agent's
`resolveModel()` then creates a *brand new* OpenAI provider using only
`OPENAI_API_KEY`, losing all Azure configuration (baseURL, api-key
header, api-version query param).
The V1 adapter's `process()` method — which correctly uses the custom
OpenAI instance — is dead code. It's never called. Only `.provider` and
`.model` strings are extracted.
```
User configures Azure OpenAI instance →
OpenAIAdapter stores it →
CopilotRuntime extracts only "openai"/"gpt-4o" strings →
BuiltInAgent.resolveModel("openai/gpt-4o") →
createOpenAI({ apiKey: OPENAI_API_KEY }) →
💥 API key missing / wrong endpoint
```
## Workaround
Until this is merged, users can bypass the broken adapter entirely by
using `BuiltInAgent` directly with the Vercel AI SDK Azure provider:
```typescript
import { createAzure } from "@ai-sdk/azure";
import { CopilotRuntime, copilotRuntimeNodeHttpEndpoint } from "@copilotkit/runtime";
import { BuiltInAgent } from "@copilotkitnext/agent";
const azure = createAzure({
resourceName: process.env.AZURE_RESOURCE_NAME,
apiKey: process.env.AZURE_API_KEY,
});
const runtime = new CopilotRuntime({
agents: {
default: new BuiltInAgent({
model: azure("your-deployment-name"),
}),
},
});
// Then use copilotRuntimeNodeHttpEndpoint as usual — no serviceAdapter needed
```
This works because `BuiltInAgent` already accepts `LanguageModel`
instances and `resolveModel()` passes them straight through.
## The Fix
Add an optional `getLanguageModel()` method to `CopilotServiceAdapter`.
Adapters that accept custom SDK instances (OpenAI, Anthropic, Groq) now
implement this to produce a pre-configured Vercel AI SDK `LanguageModel`
that carries the full provider configuration. `CopilotRuntime` prefers
this over the string format when creating `BuiltInAgent`.
**Backward compatible**: `getLanguageModel()` is optional on the
interface, so existing custom adapters continue to work unchanged.
| File | Change |
|------|--------|
| `service-adapter.ts` | Add optional `getLanguageModel()` to
`CopilotServiceAdapter` interface |
| `openai-adapter.ts` | Implement `getLanguageModel()` using
`createOpenAI` with the configured instance's `baseURL`/`apiKey` |
| `anthropic-adapter.ts` | Implement `getLanguageModel()` using
`createAnthropic` |
| `groq-adapter.ts` | Implement `getLanguageModel()` using
`createOpenAI` (Groq is OpenAI-compatible) |
| `copilot-runtime.ts` | Prefer `serviceAdapter.getLanguageModel()` over
string model when creating `BuiltInAgent` |
| `package.json` | Add `ai`, `@ai-sdk/openai`, `@ai-sdk/anthropic`
dependencies |
## Test plan
- [x] V2 agent tests pass (101/101)
- [x] V1 runtime build succeeds with no type errors
- [x] V1 runtime tests: same pass/fail as `main` (pre-existing failures
unrelated to this change)
- [ ] Manual: configure Azure OpenAI via `OpenAIAdapter` without
`OPENAI_API_KEY` — should work without `AI_LoadAPIKeyError`
- [ ] Manual: standard OpenAI via `OpenAIAdapter` with `OPENAI_API_KEY`
— should continue working
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Merges selfManagedAgents into agents__unsafe_dev_only at the component level.
No core changes, no validation, no license key checks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDK clients store defaultHeaders and fetch in a private _options field.
Extract the access pattern into getSdkClientOptions() with runtime
validation and a narrow return type, eliminating as-any from all three
adapter getLanguageModel() implementations.
Address PR review feedback: replace dynamic await import() with static
top-level imports using vi.hoisted() for mock declarations, and remove
all `as any` type casts since the SDK constructor types already accept
the options we pass.
Each test mocks the AI SDK provider factory and asserts that all options
from the SDK client instance are forwarded. Compile-time exhaustiveness
guards ensure the tests break if the AI SDK provider types add new
options that need to be handled.
Pass defaultHeaders from SDK client instances through to the AI SDK
providers. This is critical for Azure OpenAI (api-key header) and any
custom proxy setups. Also forward organization and project for OpenAI.
Service adapters (OpenAIAdapter, AnthropicAdapter, GroqAdapter) accept
custom SDK instances with provider-specific configuration (e.g. Azure
OpenAI with custom baseURL/apiKey/headers), but CopilotRuntime discards
those instances and reconstructs a plain "provider/model" string for
BuiltInAgent. The agent's resolveModel() then creates a fresh provider
using only environment variables, losing all custom configuration.
Add getLanguageModel() to CopilotServiceAdapter so adapters can produce
a pre-configured Vercel AI SDK LanguageModel. CopilotRuntime now prefers
this over the string format when creating BuiltInAgent.
FixesCopilotKit/CopilotKit#2903