Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Features Ortha CMS on GitHub

The scripted provider

Copilot plugin @orthacms/copilot-server@orthacms/copilot-admin

Why a fresh install has no copilot at all, rather than a scripted one.

Documents 0.13.1 Updated Edit this page Report a problem

On this page

@orthacms/copilot-provider-fake is a deterministic ModelProvider: no network, no clock, no randomness.

  • Private and unpublished. npm install will not resolve it.
  • Never registered by the reference server or a generated app.
  • Used by the monorepo’s apps/server-e2e to 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_ENABLEDProvidersResult
false (default)noneBoots. /api/copilot/* is 404
truenoneRefuses to boot
trueone or moreEvery 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 script

FakeProviderConfig:

OptionDefaultNotes
scriptnoneEach turn: { text?, toolCalls?, stopReason?, usage? }
models['fake']
capabilitiestool calling, streaming, 1M contextPartial override
chunkSize8Text 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.
  • calls is every ModelRequest served, in order. Assert on calls[0].tools to 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.