mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 06:04:51 +00:00
parent
e34e0c503f
commit
87a1a9ded4
7 changed files with 165 additions and 24 deletions
|
|
@ -12,6 +12,7 @@ import type {
|
|||
AssistantMessage,
|
||||
Context,
|
||||
Model,
|
||||
OpenAICompat,
|
||||
StopReason,
|
||||
StreamFunction,
|
||||
StreamOptions,
|
||||
|
|
@ -267,7 +268,8 @@ function createClient(model: Model<"openai-completions">, apiKey?: string) {
|
|||
}
|
||||
|
||||
function buildParams(model: Model<"openai-completions">, context: Context, options?: OpenAICompletionsOptions) {
|
||||
const messages = convertMessages(model, context);
|
||||
const compat = getCompat(model);
|
||||
const messages = convertMessages(model, context, compat);
|
||||
|
||||
const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
|
||||
model: model.id,
|
||||
|
|
@ -276,27 +278,20 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio
|
|||
stream_options: { include_usage: true },
|
||||
};
|
||||
|
||||
// Cerebras/xAI/Mistral dont like the "store" field
|
||||
if (
|
||||
!model.baseUrl.includes("cerebras.ai") &&
|
||||
!model.baseUrl.includes("api.x.ai") &&
|
||||
!model.baseUrl.includes("mistral.ai") &&
|
||||
!model.baseUrl.includes("chutes.ai")
|
||||
) {
|
||||
if (compat.supportsStore) {
|
||||
params.store = false;
|
||||
}
|
||||
|
||||
if (options?.maxTokens) {
|
||||
// Mistral/Chutes uses max_tokens instead of max_completion_tokens
|
||||
if (model.baseUrl.includes("mistral.ai") || model.baseUrl.includes("chutes.ai")) {
|
||||
(params as any).max_tokens = options?.maxTokens;
|
||||
if (compat.maxTokensField === "max_tokens") {
|
||||
(params as any).max_tokens = options.maxTokens;
|
||||
} else {
|
||||
params.max_completion_tokens = options?.maxTokens;
|
||||
params.max_completion_tokens = options.maxTokens;
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.temperature !== undefined) {
|
||||
params.temperature = options?.temperature;
|
||||
params.temperature = options.temperature;
|
||||
}
|
||||
|
||||
if (context.tools) {
|
||||
|
|
@ -307,27 +302,24 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio
|
|||
params.tool_choice = options.toolChoice;
|
||||
}
|
||||
|
||||
// Grok models don't like reasoning_effort
|
||||
if (options?.reasoningEffort && model.reasoning && !model.id.toLowerCase().includes("grok")) {
|
||||
if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
||||
params.reasoning_effort = options.reasoningEffort;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function convertMessages(model: Model<"openai-completions">, context: Context): ChatCompletionMessageParam[] {
|
||||
function convertMessages(
|
||||
model: Model<"openai-completions">,
|
||||
context: Context,
|
||||
compat: Required<OpenAICompat>,
|
||||
): ChatCompletionMessageParam[] {
|
||||
const params: ChatCompletionMessageParam[] = [];
|
||||
|
||||
const transformedMessages = transformMessages(context.messages, model);
|
||||
|
||||
if (context.systemPrompt) {
|
||||
// Cerebras/xAi/Mistral/Chutes don't like the "developer" role
|
||||
const useDeveloperRole =
|
||||
model.reasoning &&
|
||||
!model.baseUrl.includes("cerebras.ai") &&
|
||||
!model.baseUrl.includes("api.x.ai") &&
|
||||
!model.baseUrl.includes("mistral.ai") &&
|
||||
!model.baseUrl.includes("chutes.ai");
|
||||
const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
|
||||
const role = useDeveloperRole ? "developer" : "system";
|
||||
params.push({ role: role, content: sanitizeSurrogates(context.systemPrompt) });
|
||||
}
|
||||
|
|
@ -482,3 +474,42 @@ function mapStopReason(reason: ChatCompletionChunk.Choice["finish_reason"]): Sto
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect compatibility settings from baseUrl for known providers.
|
||||
* Returns a fully resolved OpenAICompat object with all fields set.
|
||||
*/
|
||||
function detectCompatFromUrl(baseUrl: string): Required<OpenAICompat> {
|
||||
const isNonStandard =
|
||||
baseUrl.includes("cerebras.ai") ||
|
||||
baseUrl.includes("api.x.ai") ||
|
||||
baseUrl.includes("mistral.ai") ||
|
||||
baseUrl.includes("chutes.ai");
|
||||
|
||||
const useMaxTokens = baseUrl.includes("mistral.ai") || baseUrl.includes("chutes.ai");
|
||||
|
||||
const isGrok = baseUrl.includes("api.x.ai");
|
||||
|
||||
return {
|
||||
supportsStore: !isNonStandard,
|
||||
supportsDeveloperRole: !isNonStandard,
|
||||
supportsReasoningEffort: !isGrok,
|
||||
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resolved compatibility settings for a model.
|
||||
* Uses explicit model.compat if provided, otherwise auto-detects from URL.
|
||||
*/
|
||||
function getCompat(model: Model<"openai-completions">): Required<OpenAICompat> {
|
||||
const detected = detectCompatFromUrl(model.baseUrl);
|
||||
if (!model.compat) return detected;
|
||||
|
||||
return {
|
||||
supportsStore: model.compat.supportsStore ?? detected.supportsStore,
|
||||
supportsDeveloperRole: model.compat.supportsDeveloperRole ?? detected.supportsDeveloperRole,
|
||||
supportsReasoningEffort: model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort,
|
||||
maxTokensField: model.compat.maxTokensField ?? detected.maxTokensField,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,21 @@ export type AssistantMessageEvent =
|
|||
| { type: "done"; reason: Extract<StopReason, "stop" | "length" | "toolUse">; message: AssistantMessage }
|
||||
| { type: "error"; reason: Extract<StopReason, "aborted" | "error">; error: AssistantMessage };
|
||||
|
||||
/**
|
||||
* Compatibility settings for openai-completions API.
|
||||
* Use this to override URL-based auto-detection for custom providers.
|
||||
*/
|
||||
export interface OpenAICompat {
|
||||
/** Whether the provider supports the `store` field. Default: auto-detected from URL. */
|
||||
supportsStore?: boolean;
|
||||
/** Whether the provider supports the `developer` role (vs `system`). Default: auto-detected from URL. */
|
||||
supportsDeveloperRole?: boolean;
|
||||
/** Whether the provider supports `reasoning_effort`. Default: auto-detected from URL. */
|
||||
supportsReasoningEffort?: boolean;
|
||||
/** Which field to use for max tokens. Default: auto-detected from URL. */
|
||||
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
||||
}
|
||||
|
||||
// Model interface for the unified model system
|
||||
export interface Model<TApi extends Api> {
|
||||
id: string;
|
||||
|
|
@ -170,4 +185,6 @@ export interface Model<TApi extends Api> {
|
|||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
headers?: Record<string, string>;
|
||||
/** Compatibility overrides for openai-completions API. If not set, auto-detected from baseUrl. */
|
||||
compat?: TApi extends "openai-completions" ? OpenAICompat : never;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue