mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-15 23:43:06 +03:00
🅰️ feat: Native Anthropic Provider for Custom Endpoints (#13748)
* 🅰️ feat: Native Anthropic provider for Custom Endpoints Let a custom endpoint declare `provider: anthropic` to use the native Anthropic `/v1/messages` client (the agents SDK's ChatAnthropic) against its own `baseURL`/`apiKey`/`headers`, instead of being forced through the OpenAI-compatible client. Enables Anthropic itself and Anthropic-compatible gateways (AI gateways, OpenCode Zen, etc.) as custom endpoints — including for agents and role-scoped model access. Closes #10655 (Option 1: explicit provider). - Schema: add optional `provider` (currently `anthropic`) to the custom `endpointSchema` in data-provider. - Routing: `getProviderConfig` maps a custom endpoint with `provider: anthropic` to `Providers.ANTHROPIC` (was always `Providers.OPENAI`). - Config: `initializeCustom` builds the native Anthropic config via the Anthropic `getLLMConfig` (custom baseURL/apiKey/headers) and returns `provider: anthropic`; `useLegacyContent` is left unset to match the built-in Anthropic endpoint. The OpenAI-compatible path is unchanged for endpoints without `provider`. - Summarization: `resolveSummarizationProvider` builds an Anthropic config for a cross-endpoint native-Anthropic summarization target (self-summarize already reuses the agent's client options). Title generation already resolves via `agent.endpoint`, and provider-specific handling (tool conflicts, content/PDF validation, token counting, streamUsage) already branches on `Providers.ANTHROPIC`, so it applies automatically. Note: model auto-fetch (`models.fetch`) uses the OpenAI `/models` convention and is not used for this provider — list models explicitly under `models.default`. * 🅰️ fix: Anthropic custom-endpoint param parity (Codex review) Address Codex P2 findings — the native Anthropic path must match the OpenAI-compatible path's parameter handling: - UI param set: `loadCustomEndpointsConfig` now surfaces `provider` as the client `customParams.defaultParamsEndpoint`, so the Agents model panel shows Anthropic fields (`maxOutputTokens`/`thinking`) instead of OpenAI `max_tokens` (which the native initializer ignored). An explicit non-default `defaultParamsEndpoint` still wins. - Provider override: `getProviderConfig` re-applies `provider: anthropic` after all `customEndpointConfig` resolution, so it also wins when the endpoint name collides with a known custom provider (e.g. `openrouter`) — fixing the token/context budget derived from `overrideProvider`. - Default params: the native path (and cross-endpoint Anthropic summarization) now apply `customParams.paramDefinitions` defaults via `extractDefaultParams`, matching what `getOpenAIConfig` does for the OpenAI-compatible path. Adds tests for each.
This commit is contained in:
@@ -508,6 +508,29 @@ endpoints:
|
||||
# # deploymentName: claude-3-5-haiku@20241022 # Override for this model
|
||||
|
||||
custom:
|
||||
# Anthropic-compatible Example (native `/v1/messages` API)
|
||||
# Set `provider: anthropic` to use the native Anthropic client instead of the
|
||||
# default OpenAI-compatible one — for Anthropic itself or Anthropic-compatible
|
||||
# gateways (e.g. AI gateways, OpenCode Zen). The `baseURL` must be the API root
|
||||
# the Anthropic SDK appends `/v1/messages` to. List models explicitly: model
|
||||
# auto-fetch uses the OpenAI `/models` convention and is not used for this provider.
|
||||
- name: 'Claude-Compatible'
|
||||
provider: 'anthropic'
|
||||
apiKey: '${ANTHROPIC_API_KEY}'
|
||||
baseURL: 'https://api.anthropic.com'
|
||||
# (optional) headers forwarded on every request (e.g. for a reverse proxy);
|
||||
# values support the same placeholders as built-in endpoints.
|
||||
headers:
|
||||
anthropic-version: '2023-06-01'
|
||||
models:
|
||||
default:
|
||||
- 'claude-sonnet-4-5'
|
||||
- 'claude-opus-4-5'
|
||||
fetch: false
|
||||
titleConvo: true
|
||||
titleModel: 'claude-sonnet-4-5'
|
||||
modelDisplayLabel: 'Claude (Compatible)'
|
||||
|
||||
# Groq Example
|
||||
- name: 'groq'
|
||||
apiKey: '${GROQ_API_KEY}'
|
||||
|
||||
@@ -2,6 +2,7 @@ import { logger } from '@librechat/data-schemas';
|
||||
import { Run, Providers, Constants } from '@librechat/agents';
|
||||
import {
|
||||
KnownEndpoints,
|
||||
EModelEndpoint,
|
||||
MAX_SUBAGENT_DEPTH,
|
||||
MAX_SUBAGENT_RUN_CONFIGS,
|
||||
extractEnvVariable,
|
||||
@@ -33,7 +34,9 @@ import type { BaseMessage } from '@librechat/agents/langchain/messages';
|
||||
import type { AppConfig, IUser } from '@librechat/data-schemas';
|
||||
import type { SubagentUsageEvent } from '~/agents/usage';
|
||||
import type * as t from '~/types';
|
||||
import { getLLMConfig as getAnthropicLLMConfig } from '~/endpoints/anthropic/llm';
|
||||
import { getProviderConfig } from '~/endpoints/config/providers';
|
||||
import { extractDefaultParams } from '~/endpoints/openai/llm';
|
||||
import { resolveHeaders, createSafeUser } from '~/utils/env';
|
||||
import { getOpenAIConfig } from '~/endpoints/openai/config';
|
||||
import { resolveConfigHeaders } from '~/utils/headers';
|
||||
@@ -457,6 +460,37 @@ function resolveSummarizationProvider(
|
||||
body: headerContext.requestBody,
|
||||
})
|
||||
: undefined;
|
||||
/**
|
||||
* Native Anthropic custom endpoints must build their config with the
|
||||
* Anthropic client (`/v1/messages`), not `getOpenAIConfig` (which would emit
|
||||
* OpenAI-shaped requests). The self-summarize case is handled earlier by
|
||||
* `isSameEndpointAsAgent`; this covers summarizing against a *different*
|
||||
* Anthropic-native custom endpoint.
|
||||
*/
|
||||
if (customEndpointConfig.provider === EModelEndpoint.anthropic) {
|
||||
const { llmConfig } = getAnthropicLLMConfig(apiKey, {
|
||||
modelOptions: {},
|
||||
proxy: process.env.PROXY ?? undefined,
|
||||
reverseProxyUrl: baseURL,
|
||||
headers: resolvedHeaders,
|
||||
addParams: customEndpointConfig.addParams,
|
||||
dropParams: customEndpointConfig.dropParams,
|
||||
defaultParams: extractDefaultParams(customEndpointConfig.customParams?.paramDefinitions),
|
||||
});
|
||||
const { apiKey: resolvedApiKey, ...llmConfigOverrides } = llmConfig as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const clientOverrides: SummarizationClientOverrides = { ...llmConfigOverrides };
|
||||
if (typeof resolvedApiKey === 'string') {
|
||||
clientOverrides.apiKey = resolvedApiKey;
|
||||
}
|
||||
/** Strip the default model so the user-supplied `summarization.model` wins. */
|
||||
delete clientOverrides.model;
|
||||
delete clientOverrides.modelName;
|
||||
return { provider: Providers.ANTHROPIC, clientOverrides };
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the endpoint config through `getOpenAIConfig` so summarization
|
||||
* inherits the same `headers`, `defaultQuery`, `addParams`/`dropParams`,
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AppConfig } from '@librechat/data-schemas';
|
||||
import { getProviderConfig, providerConfigMap, resolveTitleTiming } from './providers';
|
||||
|
||||
const buildAppConfig = (
|
||||
customEndpoints: Array<{ name: string; baseURL?: string; apiKey?: string }>,
|
||||
customEndpoints: Array<{ name: string; baseURL?: string; apiKey?: string; provider?: string }>,
|
||||
): AppConfig =>
|
||||
({
|
||||
endpoints: {
|
||||
@@ -96,6 +96,51 @@ describe('getProviderConfig', () => {
|
||||
getProviderConfig({ provider: 'openrouter', appConfig: buildAppConfig([]) }),
|
||||
).toThrow('Provider openrouter not supported');
|
||||
});
|
||||
|
||||
it('routes a custom endpoint with provider:anthropic to the Anthropic client', () => {
|
||||
const appConfig = buildAppConfig([
|
||||
{
|
||||
name: 'Claude-Compatible',
|
||||
baseURL: 'https://gateway.example.com',
|
||||
apiKey: 'sk-ant',
|
||||
provider: EModelEndpoint.anthropic,
|
||||
},
|
||||
]);
|
||||
|
||||
const result = getProviderConfig({ provider: 'Claude-Compatible', appConfig });
|
||||
|
||||
expect(result.overrideProvider).toBe(Providers.ANTHROPIC);
|
||||
expect(result.customEndpointConfig?.provider).toBe(EModelEndpoint.anthropic);
|
||||
});
|
||||
|
||||
it('defaults a custom endpoint without provider to the OpenAI-compatible client', () => {
|
||||
const appConfig = buildAppConfig([
|
||||
{ name: 'My-LLM', baseURL: 'https://api.example.com/v1', apiKey: 'sk-test' },
|
||||
]);
|
||||
|
||||
const result = getProviderConfig({ provider: 'My-LLM', appConfig });
|
||||
|
||||
expect(result.overrideProvider).toBe(Providers.OPENAI);
|
||||
expect(result.customEndpointConfig?.name).toBe('My-LLM');
|
||||
});
|
||||
|
||||
it('applies provider:anthropic even when the endpoint name collides with a known custom provider', () => {
|
||||
// `openrouter` resolves via `providerConfigMap` first (skipping the generic
|
||||
// custom branch); the override must still be re-applied from the config so
|
||||
// overrideProvider-derived values (token/context budget) use the Anthropic map.
|
||||
const appConfig = buildAppConfig([
|
||||
{
|
||||
name: 'openrouter',
|
||||
baseURL: 'https://gateway.example.com',
|
||||
apiKey: 'sk-ant',
|
||||
provider: EModelEndpoint.anthropic,
|
||||
},
|
||||
]);
|
||||
|
||||
const result = getProviderConfig({ provider: 'openrouter', appConfig });
|
||||
|
||||
expect(result.overrideProvider).toBe(Providers.ANTHROPIC);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveTitleTiming', () => {
|
||||
|
||||
@@ -198,6 +198,18 @@ export function getProviderConfig({
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom endpoints default to the OpenAI-compatible client. An explicit
|
||||
* `provider: anthropic` routes them through the native Anthropic `/v1/messages`
|
||||
* client (`initializeCustom` builds the right config). Applied here — after all
|
||||
* `customEndpointConfig` resolution — so it also wins when the endpoint name
|
||||
* collides with a known custom-provider (e.g. `openrouter`), ensuring
|
||||
* `overrideProvider`-derived values (token/context budget) use the Anthropic map.
|
||||
*/
|
||||
if (customEndpointConfig?.provider === EModelEndpoint.anthropic) {
|
||||
overrideProvider = Providers.ANTHROPIC;
|
||||
}
|
||||
|
||||
return {
|
||||
getOptions,
|
||||
overrideProvider,
|
||||
|
||||
44
packages/api/src/endpoints/custom/config.spec.ts
Normal file
44
packages/api/src/endpoints/custom/config.spec.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import type { TCustomEndpoints } from 'librechat-data-provider';
|
||||
import { loadCustomEndpointsConfig } from './config';
|
||||
|
||||
const baseEndpoint = {
|
||||
apiKey: 'sk-test',
|
||||
baseURL: 'https://gateway.example.com',
|
||||
models: { default: ['claude-sonnet-4-5'] },
|
||||
};
|
||||
|
||||
describe('loadCustomEndpointsConfig – native provider param set', () => {
|
||||
it('synthesizes defaultParamsEndpoint from provider so the UI shows the right params', () => {
|
||||
const config = loadCustomEndpointsConfig([
|
||||
{ ...baseEndpoint, name: 'Claude-Compatible', provider: EModelEndpoint.anthropic },
|
||||
] as unknown as TCustomEndpoints);
|
||||
|
||||
expect(config?.['Claude-Compatible']?.customParams?.defaultParamsEndpoint).toBe(
|
||||
EModelEndpoint.anthropic,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not set defaultParamsEndpoint for endpoints without a provider', () => {
|
||||
const config = loadCustomEndpointsConfig([
|
||||
{ ...baseEndpoint, name: 'My-LLM' },
|
||||
] as unknown as TCustomEndpoints);
|
||||
|
||||
expect(config?.['My-LLM']?.customParams).toBeUndefined();
|
||||
});
|
||||
|
||||
it('respects an explicit non-default defaultParamsEndpoint over the provider', () => {
|
||||
const config = loadCustomEndpointsConfig([
|
||||
{
|
||||
...baseEndpoint,
|
||||
name: 'Claude-Compatible',
|
||||
provider: EModelEndpoint.anthropic,
|
||||
customParams: { defaultParamsEndpoint: EModelEndpoint.google },
|
||||
},
|
||||
] as unknown as TCustomEndpoints);
|
||||
|
||||
expect(config?.['Claude-Compatible']?.customParams?.defaultParamsEndpoint).toBe(
|
||||
EModelEndpoint.google,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -35,17 +35,31 @@ export function loadCustomEndpointsConfig(
|
||||
iconURL,
|
||||
modelDisplayLabel,
|
||||
customParams,
|
||||
provider,
|
||||
} = endpoint;
|
||||
const name = normalizeEndpointName(configName);
|
||||
|
||||
const resolvedApiKey = extractEnvVariable(apiKey ?? '');
|
||||
const resolvedBaseURL = extractEnvVariable(baseURL ?? '');
|
||||
|
||||
/**
|
||||
* A native `provider` (e.g. anthropic) implies its parameter set. Surface it
|
||||
* as `defaultParamsEndpoint` so the client param panel shows the right fields
|
||||
* (e.g. `maxOutputTokens`/`thinking` for Anthropic, not OpenAI `max_tokens`),
|
||||
* unless an admin explicitly chose a non-default `defaultParamsEndpoint`.
|
||||
*/
|
||||
const resolvedCustomParams =
|
||||
provider != null &&
|
||||
(customParams?.defaultParamsEndpoint == null ||
|
||||
customParams.defaultParamsEndpoint === EModelEndpoint.custom)
|
||||
? { ...customParams, defaultParamsEndpoint: provider }
|
||||
: customParams;
|
||||
|
||||
customEndpointsConfig[name] = {
|
||||
type: EModelEndpoint.custom,
|
||||
userProvide: isUserProvided(resolvedApiKey),
|
||||
userProvideURL: isUserProvided(resolvedBaseURL),
|
||||
customParams,
|
||||
customParams: resolvedCustomParams,
|
||||
modelDisplayLabel,
|
||||
iconURL,
|
||||
};
|
||||
|
||||
@@ -394,3 +394,93 @@ describe('initializeCustom – token-config fetch header forwarding', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeCustom – native Anthropic provider', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function createAnthropicParams(
|
||||
config: Record<string, unknown>,
|
||||
model_parameters: Record<string, unknown> = { model: 'claude-sonnet-4-5' },
|
||||
): BaseInitializeParams {
|
||||
mockGetCustomEndpointConfig.mockReturnValue(config);
|
||||
return {
|
||||
req: {
|
||||
user: { id: 'user-1' },
|
||||
body: { conversationId: 'convo-1' },
|
||||
config: {},
|
||||
} as unknown as BaseInitializeParams['req'],
|
||||
endpoint: 'Claude-Compatible',
|
||||
model_parameters,
|
||||
db: {
|
||||
getUserKeyValues: jest.fn(),
|
||||
getUserKey: jest.fn(),
|
||||
} as unknown as BaseInitializeParams['db'],
|
||||
};
|
||||
}
|
||||
|
||||
it('builds a native Anthropic config pointed at the custom baseURL/apiKey', async () => {
|
||||
const params = createAnthropicParams({
|
||||
provider: 'anthropic',
|
||||
apiKey: 'sk-ant-custom',
|
||||
baseURL: 'https://gateway.example.com',
|
||||
headers: { 'anthropic-version': '2023-06-01' },
|
||||
models: { default: ['claude-sonnet-4-5'] },
|
||||
});
|
||||
|
||||
const options = await initializeCustom(params);
|
||||
|
||||
/** Routed to the native Anthropic client, not the OpenAI-compatible one */
|
||||
expect(mockGetOpenAIConfig).not.toHaveBeenCalled();
|
||||
expect(options.provider).toBe('anthropic');
|
||||
/** Custom baseURL/key wired into the native Anthropic config */
|
||||
expect(options.llmConfig).toHaveProperty('anthropicApiUrl', 'https://gateway.example.com');
|
||||
expect(options.llmConfig).toHaveProperty('apiKey', 'sk-ant-custom');
|
||||
/** Configured header attached (kept unresolved for request-time resolution) */
|
||||
const defaultHeaders = (
|
||||
options.llmConfig as { clientOptions?: { defaultHeaders?: Record<string, string> } }
|
||||
).clientOptions?.defaultHeaders;
|
||||
expect(defaultHeaders?.['anthropic-version']).toBe('2023-06-01');
|
||||
/** Native Anthropic path must NOT use OpenAI legacy content formatting */
|
||||
expect(options.useLegacyContent).toBeUndefined();
|
||||
});
|
||||
|
||||
it('applies customParams.paramDefinitions defaults on the native path', async () => {
|
||||
const params = createAnthropicParams({
|
||||
provider: 'anthropic',
|
||||
apiKey: 'sk-ant-custom',
|
||||
baseURL: 'https://gateway.example.com',
|
||||
models: { default: ['claude-sonnet-4-5'] },
|
||||
customParams: {
|
||||
defaultParamsEndpoint: 'anthropic',
|
||||
paramDefinitions: [{ key: 'web_search', default: true }],
|
||||
},
|
||||
});
|
||||
|
||||
const options = await initializeCustom(params);
|
||||
|
||||
/** `web_search: true` default flows through to the Anthropic web_search tool */
|
||||
expect(options.tools).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'web_search' })]),
|
||||
);
|
||||
});
|
||||
|
||||
it('still uses the OpenAI-compatible client when no provider is set', async () => {
|
||||
const params = createAnthropicParams({
|
||||
apiKey: 'sk-test',
|
||||
baseURL: 'https://api.example.com/v1',
|
||||
models: { default: ['gpt-4o'] },
|
||||
});
|
||||
|
||||
const options = await initializeCustom(params);
|
||||
|
||||
expect(mockGetOpenAIConfig).toHaveBeenCalledWith(
|
||||
'sk-test',
|
||||
expect.any(Object),
|
||||
'Claude-Compatible',
|
||||
);
|
||||
expect(options.useLegacyContent).toBe(true);
|
||||
expect(options.provider).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { Providers } from '@librechat/agents';
|
||||
import {
|
||||
ErrorTypes,
|
||||
envVarRegex,
|
||||
EModelEndpoint,
|
||||
FetchTokenConfig,
|
||||
extractEnvVariable,
|
||||
} from 'librechat-data-provider';
|
||||
import type { TEndpoint } from 'librechat-data-provider';
|
||||
import type { AppConfig } from '@librechat/data-schemas';
|
||||
import type { BaseInitializeParams, InitializeResultBase, EndpointTokenConfig } from '~/types';
|
||||
import type {
|
||||
BaseInitializeParams,
|
||||
InitializeResultBase,
|
||||
EndpointTokenConfig,
|
||||
AnthropicModelOptions,
|
||||
} from '~/types';
|
||||
import { getLLMConfig as getAnthropicLLMConfig } from '~/endpoints/anthropic/llm';
|
||||
import { extractDefaultParams } from '~/endpoints/openai/llm';
|
||||
import { isUserProvided, checkUserKeyExpiry } from '~/utils';
|
||||
import { getOpenAIConfig } from '~/endpoints/openai/config';
|
||||
import { getCustomEndpointConfig } from '~/app/config';
|
||||
@@ -90,6 +99,42 @@ function buildCustomOptions(
|
||||
return customOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a native Anthropic (`/v1/messages`) config for a custom endpoint that
|
||||
* declares `provider: anthropic`, pointing the Anthropic client at the custom
|
||||
* `baseURL`/`apiKey`. Returns `provider: anthropic` so the agent uses the native
|
||||
* Anthropic client instead of the OpenAI-compatible one. Headers stay unresolved
|
||||
* here and resolve at request time via `resolveConfigHeaders`.
|
||||
*/
|
||||
function buildAnthropicCustomConfig({
|
||||
apiKey,
|
||||
baseURL,
|
||||
modelOptions,
|
||||
endpointConfig,
|
||||
}: {
|
||||
apiKey: string;
|
||||
baseURL: string;
|
||||
modelOptions: AnthropicModelOptions;
|
||||
endpointConfig: Partial<TEndpoint>;
|
||||
}): InitializeResultBase {
|
||||
const result = getAnthropicLLMConfig(apiKey, {
|
||||
modelOptions,
|
||||
proxy: PROXY ?? undefined,
|
||||
reverseProxyUrl: baseURL,
|
||||
headers: endpointConfig.headers,
|
||||
addParams: endpointConfig.addParams,
|
||||
dropParams: endpointConfig.dropParams,
|
||||
/** Apply admin `customParams.paramDefinitions` defaults (e.g. promptCache,
|
||||
* web_search, thinking) the OpenAI-compatible path gets via `getOpenAIConfig`. */
|
||||
defaultParams: extractDefaultParams(endpointConfig.customParams?.paramDefinitions),
|
||||
});
|
||||
return {
|
||||
llmConfig: result.llmConfig as InitializeResultBase['llmConfig'],
|
||||
tools: result.tools,
|
||||
provider: Providers.ANTHROPIC,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a custom endpoint client configuration.
|
||||
* This function handles custom endpoints defined in librechat.yaml, including
|
||||
@@ -233,15 +278,29 @@ export async function initializeCustom({
|
||||
};
|
||||
|
||||
const modelOptions = { ...(model_parameters ?? {}), user: userId };
|
||||
const finalClientOptions = {
|
||||
modelOptions,
|
||||
...clientOptions,
|
||||
};
|
||||
|
||||
const options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
|
||||
if (options != null) {
|
||||
(options as InitializeResultBase).useLegacyContent = true;
|
||||
(options as InitializeResultBase).endpointTokenConfig = endpointTokenConfig;
|
||||
let options: InitializeResultBase;
|
||||
if (endpointConfig.provider === EModelEndpoint.anthropic) {
|
||||
/** Native Anthropic `/v1/messages` client against the custom baseURL/apiKey.
|
||||
* `useLegacyContent` is intentionally left unset (matches the built-in
|
||||
* Anthropic endpoint, which uses native content formatting). */
|
||||
options = buildAnthropicCustomConfig({
|
||||
apiKey,
|
||||
baseURL,
|
||||
modelOptions: modelOptions as AnthropicModelOptions,
|
||||
endpointConfig,
|
||||
});
|
||||
options.endpointTokenConfig = endpointTokenConfig;
|
||||
} else {
|
||||
const finalClientOptions = {
|
||||
modelOptions,
|
||||
...clientOptions,
|
||||
};
|
||||
options = getOpenAIConfig(apiKey, finalClientOptions, endpoint);
|
||||
if (options != null) {
|
||||
options.useLegacyContent = true;
|
||||
options.endpointTokenConfig = endpointTokenConfig;
|
||||
}
|
||||
}
|
||||
|
||||
const streamRate = clientOptions.streamRate as number | undefined;
|
||||
|
||||
@@ -841,6 +841,14 @@ export const endpointSchema = baseEndpointSchema.merge(
|
||||
}),
|
||||
iconURL: z.string().optional(),
|
||||
modelDisplayLabel: z.string().optional(),
|
||||
/**
|
||||
* Forces the endpoint to use a provider's native client / request format
|
||||
* instead of the default OpenAI-compatible client. Currently supports
|
||||
* `anthropic`, for endpoints that speak the Anthropic `/v1/messages` API
|
||||
* (Anthropic itself or Anthropic-compatible gateways). Omit for
|
||||
* OpenAI-compatible endpoints.
|
||||
*/
|
||||
provider: z.literal(EModelEndpoint.anthropic).optional(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
addParams: addParamsSchema.optional(),
|
||||
dropParams: z.array(z.string()).optional(),
|
||||
|
||||
Reference in New Issue
Block a user