Skip to content

fix [name, options] syntax for transformers registered as instances#1390

Open
gigi206 wants to merge 1 commit into
musistudio:mainfrom
gigi206:fix/transformer-options-with-instance-transformers
Open

fix [name, options] syntax for transformers registered as instances#1390
gigi206 wants to merge 1 commit into
musistudio:mainfrom
gigi206:fix/transformer-options-with-instance-transformers

Conversation

@gigi206
Copy link
Copy Markdown

@gigi206 gigi206 commented May 11, 2026

Problem

The configuration syntax "use": [["TransformerName", { ...options }]] is documented for passing options to a transformer constructor, and the built-in AnthropicTransformer exposes a UseBearer option through its constructor:

constructor(private readonly options?: TransformerOptions) {
  this.useBearer = this.options?.UseBearer ?? false;
}

However, attempting to use it from config:

{
  "transformer": { "use": [["Anthropic", { "UseBearer": true }]] }
}

logs:

<providerName> provider registered error: TypeError: o is not a constructor

and the provider silently fails to load.

Root cause

In ProviderService.initializeFromProvidersArray, the array form is handled by:

const Constructor = this.transformerService.getTransformer(transformer[0]);
if (Constructor) {
  return new (Constructor as TransformerConstructor)(transformer[1]);
}

But getTransformer returns either a class or an already-built instance, depending on how the transformer was registered:

// in registerDefaultTransformersInternal()
if ("TransformerName" in e && typeof e.TransformerName === "string") {
  this.registerTransformer(e.TransformerName, e);   // class
} else {
  const t = new e();
  this.registerTransformer(t.name, t);              // instance
}

Built-in transformers without a static TransformerName (Anthropic, Gemini, OpenAI, Deepseek, etc.) are registered as instances, so new instance(options) throws.

The string-only form already handled both cases with a typeof === 'function' check; the array-with-options form did not.

Fix

Check whether the resolved value is a class (function) or an instance:

  • Class: instantiate as before.
  • Instance: build a fresh instance via its .constructor so the requested options take effect without mutating the shared instance registered globally.

Applied to both transformer.use and model-specific transformer[model].use.

Use case

{
  "transformer": { "use": [["Anthropic", { "UseBearer": true }]] }
}

Now correctly switches the Anthropic transformer to send Authorization: Bearer ... instead of x-api-key, useful for Anthropic-compatible endpoints whose auth scheme differs from Anthropic's official endpoint.

When a config uses the array form `["TransformerName", { options }]`,
ccr resolved the name then called `new` unconditionally. Built-in
transformers without a `static TransformerName` (Anthropic, Gemini,
OpenAI, Deepseek, ...) are registered as already-built instances, so
the `new` threw `TypeError: o is not a constructor` and the provider
silently failed to load.

Detect whether the resolved transformer is a class (function) or an
instance. For instances, build a fresh one via `.constructor` so the
requested options take effect without mutating the shared instance.

Applied to both `use` and model-specific `use` entries.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant