can1357/oh-my-pi · error · AIError.ConfigurationError

Cannot register custom API "${api}": built-in API names are

Error message

Cannot register custom API "${api}": built-in API names are reserved.

What it means

registerCustomApi lets applications plug in custom streaming APIs, but built-in API names (the KnownApi set in BUILTIN_APIS) are reserved so routing and model dispatch stay unambiguous. assertCustomApiName checks the name against that set and throws ConfigurationError if it collides.

Source

Thrown at packages/ai/src/api-registry.ts:65

	options?: StreamOptions,
) => AssistantMessageEventStream;
export type CustomStreamSimpleFn = (
	model: Model<Api>,
	context: Context,
	options?: SimpleStreamOptions,
) => AssistantMessageEventStream;

export interface RegisteredCustomApi {
	stream: CustomStreamFn;
	streamSimple: CustomStreamSimpleFn;
	sourceId?: string;
}

const customApiRegistry = new Map<string, RegisteredCustomApi>();

function assertCustomApiName(api: string): void {
	if (BUILTIN_APIS.has(api as KnownApi)) {
		throw new AIError.ConfigurationError(`Cannot register custom API "${api}": built-in API names are reserved.`);
	}
}

/**
 * Register a custom API streaming function.
 */
export function registerCustomApi(
	api: string,
	streamSimple: CustomStreamSimpleFn,
	sourceId?: string,
	stream?: CustomStreamFn,
): void {
	assertCustomApiName(api);
	customApiRegistry.set(api, {
		stream: stream ?? ((model, context, options) => streamSimple(model, context, options as SimpleStreamOptions)),
		streamSimple,
		sourceId,
	});

View on GitHub (pinned to 9690622007)

Solutions

  1. Choose a distinct namespaced custom API name, e.g. "mycompany-anthropic-proxy"
  2. If the goal is to change built-in behavior, use the model's api field to point at your custom API name instead of re-registering the built-in name
  3. Print or log BUILTIN_APIS / the KnownApi union to pick a non-colliding name

Example fix

// before
registerCustomApi("anthropic-messages", myHandler) // reserved
// after
registerCustomApi("acme-anthropic-messages", myHandler)
// then set model.api = "acme-anthropic-messages"
Defensive patterns

Strategy: validation

Validate before calling

import { BUILTIN_APIS } from "@oh-my-pi/pi-ai"; // or the registry module exporting the set
if (BUILTIN_APIS.has(myApiName)) {
  throw new Error(`API name "${myApiName}" is reserved; pick a namespaced custom name`);
}
registerCustomApi(myApiName, handler);

Try / catch

try {
  registerCustomApi(name, handler);
} catch (err) {
  if (err instanceof AIError.ConfigurationError && err.message.includes("reserved")) {
    registerCustomApi(`custom-${name}`, handler);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling registerCustomApi with a name equal to a built-in API identifier (e.g. "anthropic-messages", "openai-completions", "openai-responses" or any other entry of BUILTIN_APIS).

Common situations: Trying to override/patch a built-in provider's behavior via the custom registry; copy-pasting a model's api field as the custom name; typos like "openai-chat" that collide with an actual built-in id.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/203f96a516ac902f. Report an issue: GitHub.