Files
openclaw/extensions/openai/shared.ts
2026-04-14 21:52:16 +05:30

124 lines
4.1 KiB
TypeScript

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { findCatalogTemplate } from "openclaw/plugin-sdk/provider-catalog-shared";
import {
cloneFirstTemplateModel,
matchesExactOrPrefix,
type ProviderPlugin,
} from "openclaw/plugin-sdk/provider-model-shared";
import { OPENAI_RESPONSES_STREAM_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { buildOpenAIReplayPolicy } from "./replay-policy.js";
import {
resolveOpenAITransportTurnState,
resolveOpenAIWebSocketSessionPolicy,
} from "./transport-policy.js";
type SyntheticOpenAIModelCatalogCost = {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
};
type SyntheticOpenAIModelCatalogEntry = {
provider: string;
id: string;
name: string;
reasoning?: boolean;
input?: ("text" | "image")[];
contextWindow?: number;
contextTokens?: number;
cost?: SyntheticOpenAIModelCatalogCost;
};
export const OPENAI_API_BASE_URL = "https://api.openai.com/v1";
export function toOpenAIDataUrl(buffer: Buffer, mimeType: string): string {
return `data:${mimeType};base64,${buffer.toString("base64")}`;
}
export function resolveConfiguredOpenAIBaseUrl(cfg: OpenClawConfig | undefined): string {
return normalizeOptionalString(cfg?.models?.providers?.openai?.baseUrl) ?? OPENAI_API_BASE_URL;
}
function hasSupportedOpenAIResponsesTransport(
transport: unknown,
): transport is "auto" | "sse" | "websocket" {
return transport === "auto" || transport === "sse" || transport === "websocket";
}
export function defaultOpenAIResponsesExtraParams(
extraParams: Record<string, unknown> | undefined,
options?: { openaiWsWarmup?: boolean },
): Record<string, unknown> | undefined {
const hasSupportedTransport = hasSupportedOpenAIResponsesTransport(extraParams?.transport);
const hasExplicitWarmup = typeof extraParams?.openaiWsWarmup === "boolean";
const shouldDefaultWarmup = options?.openaiWsWarmup === true;
if (hasSupportedTransport && (!shouldDefaultWarmup || hasExplicitWarmup)) {
return extraParams;
}
return {
...extraParams,
...(hasSupportedTransport ? {} : { transport: "auto" }),
...(shouldDefaultWarmup && !hasExplicitWarmup ? { openaiWsWarmup: true } : {}),
};
}
type OpenAIResponsesProviderHooks = Pick<
ProviderPlugin,
| "buildReplayPolicy"
| "prepareExtraParams"
| "wrapStreamFn"
| "resolveTransportTurnState"
| "resolveWebSocketSessionPolicy"
>;
const resolveOpenAIResponsesTransportTurnState: NonNullable<
OpenAIResponsesProviderHooks["resolveTransportTurnState"]
> = (ctx) => resolveOpenAITransportTurnState(ctx);
const resolveOpenAIResponsesWebSocketSessionPolicy: NonNullable<
OpenAIResponsesProviderHooks["resolveWebSocketSessionPolicy"]
> = (ctx) => resolveOpenAIWebSocketSessionPolicy(ctx);
export function buildOpenAIResponsesProviderHooks(options?: {
openaiWsWarmup?: boolean;
}): OpenAIResponsesProviderHooks {
return {
buildReplayPolicy: buildOpenAIReplayPolicy,
prepareExtraParams: (ctx) => defaultOpenAIResponsesExtraParams(ctx.extraParams, options),
...OPENAI_RESPONSES_STREAM_HOOKS,
resolveTransportTurnState: resolveOpenAIResponsesTransportTurnState,
resolveWebSocketSessionPolicy: resolveOpenAIResponsesWebSocketSessionPolicy,
};
}
export function buildOpenAISyntheticCatalogEntry(
template: ReturnType<typeof findCatalogTemplate>,
entry: {
id: string;
reasoning: boolean;
input: readonly ("text" | "image")[];
contextWindow: number;
contextTokens?: number;
cost?: SyntheticOpenAIModelCatalogCost;
},
): SyntheticOpenAIModelCatalogEntry | undefined {
if (!template) {
return undefined;
}
return {
...template,
id: entry.id,
name: entry.id,
reasoning: entry.reasoning,
input: [...entry.input],
contextWindow: entry.contextWindow,
...(entry.contextTokens === undefined ? {} : { contextTokens: entry.contextTokens }),
...(entry.cost === undefined ? {} : { cost: entry.cost }),
};
}
export { cloneFirstTemplateModel, findCatalogTemplate, matchesExactOrPrefix };