@orthacms/copilot-provider-fake is a deterministic ModelProvider: no
network, no clock, no randomness.
- Private and unpublished.
npm installwill not resolve it. - Never registered by the reference server or a generated app.
- Used by the monorepo’s
apps/server-e2eto drive the whole tool loop with no key.
Earlier versions registered it as a fallback
That made a deployment with a lost key answer every question from a script.
Now a keyless install has no copilot, and COPILOT_ENABLED=true with no
backend refuses to boot.
What a keyless install does
The reference copilotProviders() registers only configured backends. No keys,
empty list:
export function copilotProviders(config: OrthaCmsConfig): ProviderRegistration[] {
const { claude, ollama } = config.plugins.copilot.providers;
return [
...(claude ? [{ name: 'claude', provider: createAnthropicProvider(claude) }] : []),
...(ollama ? [{ name: 'ollama', provider: createOpenAiProvider(ollama) }] : [])
];
}COPILOT_ENABLED | Providers | Result |
|---|---|---|
false (default) | none | Boots. /api/copilot/* is 404 |
true | none | Refuses to boot |
true | one or more | Every backend is one somebody configured |
For real answers without a hosted key, run a local model through the OpenAI-compatible adapter.
Driving it in a test (monorepo only)
import { createFakeProvider } from '@orthacms/copilot-provider-fake';
const fake = createFakeProvider({
models: ['fake'],
script: [
{ toolCalls: [{ name: 'admin_content_search', input: { typeName: 'article', search: 'pricing' } }] },
{ text: 'Found two entries about pricing.' }
]
});
CopilotPlugin({
providers: [{ name: 'fake', provider: fake }],
config: { enabled: true, maxOutputTokens: 1024 }
});
// after the run:
expect(fake.calls).toHaveLength(2);
expect(fake.calls[0].tools?.map((t) => t.name)).not.toContain('content_propose_update');
fake.reset(); // clears calls, rewinds the scriptFakeProviderConfig:
| Option | Default | Notes |
|---|---|---|
script | none | Each turn: { text?, toolCalls?, stopReason?, usage? } |
models | ['fake'] | |
capabilities | tool calling, streaming, 1M context | Partial override |
chunkSize | 8 | Text is split into text-delta chunks of this size |
- With no
script, every turn answers a fixed sentence saying no model was called. - Running past the end of a script throws. It never invents a turn.
callsis everyModelRequestserved, in order. Assert oncalls[0].toolsto prove a viewer’s run was never offered write tools.
The identity plugin's scripted provider is different
@orthacms/identity-provider-fake is published
and installed in every generated app, but registered in none.
See writing a provider for the port and its conformance kit.