Files
Jordan Ritter 3f1259d060 fix: propagate custom provider config from service adapters to BuiltInAgent (#3283)
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)
2026-02-26 18:36:11 -04:00
..