Refactor proxy-core surfaces and transformer boundaries (#163)

* refactor: add canonical request contracts

* test: harden transformer boundary contracts

* refactor: adapt protocol transformers to canonical contract

* refactor: add cli profile registry

* refactor: extract provider profiles

* refactor: add conductor and provider executors

* refactor: add models surface

* refactor: migrate responses surface

* refactor: migrate chat surface

* refactor: migrate gemini surface

* refactor: migrate file surfaces

* test: align runtime executor undici mock

* fix: preserve compact SSE closeout payloads

* feat: support all-api-hub v2 backup imports

* fix: aggregate codex oauth SSE final payloads

* fix: harden responses SSE failure handling
This commit is contained in:
Cita
2026-03-19 16:07:56 +08:00
committed by GitHub
parent c0c993f448
commit 24483c28eb
75 changed files with 8333 additions and 3574 deletions
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import {
canonicalAttachmentFromInputFileBlock,
canonicalAttachmentToNormalizedInputFile,
} from './attachments.js';
describe('canonical attachment helpers', () => {
it('normalizes input_file blocks and prefers file_data over file_url and file_id', () => {
const attachment = canonicalAttachmentFromInputFileBlock({
type: 'input_file',
file_data: 'data:application/pdf;base64,QUJD',
file_url: 'https://example.com/brief.pdf',
file_id: 'file_ignored',
filename: 'brief.pdf',
});
expect(attachment).toEqual({
kind: 'file',
sourceType: 'input_file',
fileData: 'data:application/pdf;base64,QUJD',
filename: 'brief.pdf',
mimeType: 'application/pdf',
});
expect(canonicalAttachmentToNormalizedInputFile(attachment!)).toEqual({
sourceType: 'input_file',
fileData: 'data:application/pdf;base64,QUJD',
filename: 'brief.pdf',
mimeType: 'application/pdf',
hadDataUrl: true,
});
});
it('preserves file_url when no file_data exists', () => {
const attachment = canonicalAttachmentFromInputFileBlock({
type: 'file',
file: {
file_url: 'https://example.com/report.json',
filename: 'report.json',
mime_type: 'application/json',
},
});
expect(attachment).toEqual({
kind: 'file',
sourceType: 'file',
fileUrl: 'https://example.com/report.json',
filename: 'report.json',
mimeType: 'application/json',
});
});
});
@@ -0,0 +1,57 @@
import {
normalizeInputFileBlock,
type NormalizedInputFile,
} from '../shared/inputFile.js';
export type CanonicalAttachment = {
kind: 'file';
sourceType?: 'file' | 'input_file';
fileId?: string;
fileUrl?: string;
fileData?: string;
filename?: string;
mimeType?: string | null;
};
function cloneOptionalString(value: string | null | undefined): string | undefined {
return typeof value === 'string' && value.length > 0 ? value : undefined;
}
export function canonicalAttachmentFromNormalizedInputFile(
file: NormalizedInputFile,
): CanonicalAttachment {
return {
kind: 'file',
sourceType: file.sourceType,
...(cloneOptionalString(file.fileId) ? { fileId: file.fileId } : {}),
...(cloneOptionalString(file.fileUrl) ? { fileUrl: file.fileUrl } : {}),
...(cloneOptionalString(file.fileData) ? { fileData: file.fileData } : {}),
...(cloneOptionalString(file.filename) ? { filename: file.filename } : {}),
...(file.mimeType !== undefined ? { mimeType: file.mimeType } : {}),
};
}
export function canonicalAttachmentFromInputFileBlock(
item: Record<string, unknown>,
): CanonicalAttachment | null {
const normalized = normalizeInputFileBlock(item);
if (!normalized) return null;
return canonicalAttachmentFromNormalizedInputFile(normalized);
}
export function canonicalAttachmentToNormalizedInputFile(
attachment: CanonicalAttachment,
): NormalizedInputFile {
const hadDataUrl = typeof attachment.fileData === 'string'
? /^data:[^;,]+;base64,/i.test(attachment.fileData)
: undefined;
return {
...(attachment.sourceType ? { sourceType: attachment.sourceType } : {}),
...(cloneOptionalString(attachment.fileId) ? { fileId: attachment.fileId } : {}),
...(cloneOptionalString(attachment.fileUrl) ? { fileUrl: attachment.fileUrl } : {}),
...(cloneOptionalString(attachment.fileData) ? { fileData: attachment.fileData } : {}),
...(cloneOptionalString(attachment.filename) ? { filename: attachment.filename } : {}),
...(attachment.mimeType !== undefined ? { mimeType: attachment.mimeType } : {}),
...(hadDataUrl !== undefined ? { hadDataUrl } : {}),
};
}
@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest';
import { normalizeCanonicalReasoningRequest } from './reasoning.js';
describe('canonical reasoning helpers', () => {
it('normalizes reasoning config and keeps non-reasoning include entries in metadata', () => {
const normalized = normalizeCanonicalReasoningRequest({
include: [' reasoning.encrypted_content ', '', 'message.input_image.image_url'],
reasoning: {
effort: 'high',
},
reasoning_budget: '2048',
reasoning_summary: 'detailed',
});
expect(normalized.reasoning).toEqual({
effort: 'high',
budgetTokens: 2048,
summary: 'detailed',
includeEncryptedContent: true,
});
expect(normalized.metadata).toEqual({
include: ['message.input_image.image_url'],
});
});
it('returns undefined when no reasoning hints are present', () => {
expect(normalizeCanonicalReasoningRequest({})).toEqual({});
});
});
@@ -0,0 +1,98 @@
import type { TransformerMetadata } from '../shared/normalized.js';
import type {
CanonicalReasoningEffort,
CanonicalReasoningRequest,
} from './types.js';
type CanonicalReasoningNormalizationInput = {
include?: unknown;
reasoning?: unknown;
reasoning_effort?: unknown;
reasoning_budget?: unknown;
reasoning_summary?: unknown;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function asTrimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function toFiniteInteger(value: unknown): number | undefined {
if (typeof value === 'number' && Number.isFinite(value)) {
return Math.trunc(value);
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!trimmed) return undefined;
const parsed = Number(trimmed);
if (Number.isFinite(parsed)) return Math.trunc(parsed);
}
return undefined;
}
function normalizeReasoningEffort(value: unknown): CanonicalReasoningEffort | undefined {
const effort = asTrimmedString(value).toLowerCase();
switch (effort) {
case 'none':
case 'low':
case 'medium':
case 'high':
case 'max':
return effort;
default:
return undefined;
}
}
function normalizeIncludeEntries(value: unknown): string[] {
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed ? [trimmed] : [];
}
if (!Array.isArray(value)) return [];
return value
.map((item) => (typeof item === 'string' ? item.trim() : ''))
.filter((item) => item.length > 0);
}
export function normalizeCanonicalReasoningRequest(
input: CanonicalReasoningNormalizationInput,
): {
reasoning?: CanonicalReasoningRequest;
metadata?: Pick<TransformerMetadata, 'include'>;
} {
const rawReasoning = isRecord(input.reasoning) ? input.reasoning : {};
const include = normalizeIncludeEntries(input.include);
const filteredInclude = include.filter((item) => item !== 'reasoning.encrypted_content');
const effort = normalizeReasoningEffort(rawReasoning.effort ?? input.reasoning_effort);
const budgetTokens = toFiniteInteger(rawReasoning.budget_tokens ?? rawReasoning.budgetTokens ?? input.reasoning_budget);
const summary = asTrimmedString(rawReasoning.summary ?? input.reasoning_summary) || undefined;
const includeEncryptedContent = include.includes('reasoning.encrypted_content') || undefined;
const reasoning: CanonicalReasoningRequest | undefined = (
effort
|| budgetTokens !== undefined
|| summary
|| includeEncryptedContent
)
? {
...(effort ? { effort } : {}),
...(budgetTokens !== undefined ? { budgetTokens } : {}),
...(summary ? { summary } : {}),
...(includeEncryptedContent ? { includeEncryptedContent } : {}),
}
: undefined;
const metadata = filteredInclude.length > 0
? { include: filteredInclude }
: undefined;
return {
...(reasoning ? { reasoning } : {}),
...(metadata ? { metadata } : {}),
};
}
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';
import { createCanonicalRequestEnvelope } from './request.js';
describe('canonical request helpers', () => {
it('normalizes a count_tokens request without provider-owned fields', () => {
const request = createCanonicalRequestEnvelope({
operation: 'count_tokens',
surface: 'anthropic-messages',
cliProfile: 'claude_code',
requestedModel: ' claude-sonnet-4-5 ',
stream: false,
continuation: {
sessionId: ' session-1 ',
promptCacheKey: ' cache-1 ',
},
});
expect(request).toEqual({
operation: 'count_tokens',
surface: 'anthropic-messages',
cliProfile: 'claude_code',
requestedModel: 'claude-sonnet-4-5',
stream: false,
messages: [],
continuation: {
sessionId: 'session-1',
promptCacheKey: 'cache-1',
},
});
});
it('defaults generate requests to generic profile and empty collections', () => {
const request = createCanonicalRequestEnvelope({
requestedModel: 'gpt-5.2-codex',
surface: 'openai-responses',
});
expect(request).toEqual({
operation: 'generate',
surface: 'openai-responses',
cliProfile: 'generic',
requestedModel: 'gpt-5.2-codex',
stream: false,
messages: [],
});
});
});
@@ -0,0 +1,509 @@
import {
canonicalAttachmentFromInputFileBlock,
canonicalAttachmentToNormalizedInputFile,
type CanonicalAttachment,
} from './attachments.js';
import { normalizeCanonicalReasoningRequest } from './reasoning.js';
import type { CanonicalTool, CanonicalToolChoice } from './tools.js';
import type {
CanonicalContentPart,
CanonicalCliProfile,
CanonicalContinuation,
CanonicalMessage,
CanonicalMessageRole,
CanonicalOperation,
CanonicalReasoningRequest,
CanonicalRequestEnvelope,
CanonicalSurface,
} from './types.js';
import { toOpenAiChatFileBlock } from '../shared/inputFile.js';
export type CreateCanonicalRequestEnvelopeInput = {
operation?: CanonicalOperation;
surface: CanonicalSurface;
cliProfile?: CanonicalCliProfile;
requestedModel: string;
stream?: boolean;
messages?: CanonicalMessage[];
reasoning?: CanonicalReasoningRequest;
tools?: CanonicalTool[];
toolChoice?: CanonicalToolChoice;
continuation?: CanonicalContinuation;
metadata?: Record<string, unknown>;
passthrough?: Record<string, unknown>;
attachments?: CanonicalAttachment[];
};
function asTrimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : '';
}
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function cloneJsonValue<T>(value: T): T {
if (Array.isArray(value)) {
return value.map((item) => cloneJsonValue(item)) as T;
}
if (isRecord(value)) {
return Object.fromEntries(
Object.entries(value).map(([key, item]) => [key, cloneJsonValue(item)]),
) as T;
}
return value;
}
function safeJsonStringify(value: unknown): string {
try {
return JSON.stringify(value);
} catch {
return '';
}
}
function normalizeCanonicalContinuation(
continuation: CanonicalContinuation | undefined,
): CanonicalContinuation | undefined {
if (!continuation) return undefined;
const normalized: CanonicalContinuation = {
...(asTrimmedString(continuation.sessionId) ? { sessionId: asTrimmedString(continuation.sessionId) } : {}),
...(asTrimmedString(continuation.previousResponseId)
? { previousResponseId: asTrimmedString(continuation.previousResponseId) }
: {}),
...(asTrimmedString(continuation.promptCacheKey)
? { promptCacheKey: asTrimmedString(continuation.promptCacheKey) }
: {}),
...(asTrimmedString(continuation.turnState) ? { turnState: asTrimmedString(continuation.turnState) } : {}),
};
return Object.keys(normalized).length > 0 ? normalized : undefined;
}
export function createCanonicalRequestEnvelope(
input: CreateCanonicalRequestEnvelopeInput,
): CanonicalRequestEnvelope {
const requestedModel = asTrimmedString(input.requestedModel);
if (!requestedModel) {
throw new Error('canonical request requires requestedModel');
}
return {
operation: input.operation ?? 'generate',
surface: input.surface,
cliProfile: input.cliProfile ?? 'generic',
requestedModel,
stream: input.stream === true,
messages: Array.isArray(input.messages) ? input.messages : [],
...(input.reasoning ? { reasoning: input.reasoning } : {}),
...(Array.isArray(input.tools) && input.tools.length > 0 ? { tools: input.tools } : {}),
...(input.toolChoice !== undefined ? { toolChoice: input.toolChoice } : {}),
...(normalizeCanonicalContinuation(input.continuation)
? { continuation: normalizeCanonicalContinuation(input.continuation) }
: {}),
...(input.metadata ? { metadata: input.metadata } : {}),
...(input.passthrough ? { passthrough: input.passthrough } : {}),
};
}
type CanonicalRequestFromOpenAiBodyInput = {
body: Record<string, unknown>;
surface: CanonicalSurface;
cliProfile?: CanonicalCliProfile;
operation?: CanonicalOperation;
metadata?: Record<string, unknown>;
passthrough?: Record<string, unknown>;
continuation?: CanonicalContinuation;
};
function normalizeRole(value: unknown): CanonicalMessageRole {
const role = asTrimmedString(value).toLowerCase();
switch (role) {
case 'system':
case 'developer':
case 'assistant':
case 'tool':
return role;
default:
return 'user';
}
}
function openAiContentToCanonicalParts(content: unknown): CanonicalContentPart[] {
if (typeof content === 'string') {
return content ? [{ type: 'text', text: content }] : [];
}
if (!Array.isArray(content)) return [];
const parts: CanonicalContentPart[] = [];
for (const item of content) {
if (typeof item === 'string') {
if (item) parts.push({ type: 'text', text: item });
continue;
}
if (!isRecord(item)) continue;
const type = asTrimmedString(item.type).toLowerCase();
if (type === 'text' || type === 'input_text' || type === 'output_text') {
const text = asTrimmedString(item.text);
if (text) parts.push({ type: 'text', text });
continue;
}
if (type === 'reasoning' || type === 'thinking' || type === 'redacted_reasoning') {
const text = asTrimmedString(item.text ?? item.reasoning ?? item.thinking);
if (text) parts.push({ type: 'text', text, thought: true });
continue;
}
if (type === 'image_url' && isRecord(item.image_url)) {
const url = asTrimmedString(item.image_url.url);
if (url) parts.push({ type: 'image', url });
continue;
}
if (type === 'input_image' && isRecord(item.image_url)) {
const url = asTrimmedString(item.image_url.url);
if (url) parts.push({ type: 'image', url });
continue;
}
if (type === 'input_file' || type === 'file') {
const attachment = canonicalAttachmentFromInputFileBlock(item);
if (attachment) {
parts.push({
type: 'file',
...(attachment.fileId ? { fileId: attachment.fileId } : {}),
...(attachment.fileUrl ? { fileUrl: attachment.fileUrl } : {}),
...(attachment.fileData ? { fileData: attachment.fileData } : {}),
...(attachment.mimeType !== undefined ? { mimeType: attachment.mimeType } : {}),
...(attachment.filename ? { filename: attachment.filename } : {}),
});
}
continue;
}
}
return parts;
}
function parseToolChoice(rawToolChoice: unknown): CanonicalToolChoice | undefined {
if (typeof rawToolChoice === 'string') {
const normalized = rawToolChoice.trim().toLowerCase();
if (normalized === 'auto' || normalized === 'none' || normalized === 'required') return normalized;
if (normalized === 'any') return 'required';
return undefined;
}
if (!isRecord(rawToolChoice)) return undefined;
const type = asTrimmedString(rawToolChoice.type).toLowerCase();
if (type === 'auto' || type === 'none') return type;
if (type === 'any' || type === 'required') return 'required';
if (type !== 'tool') return undefined;
const name = asTrimmedString(
rawToolChoice.name
?? (isRecord(rawToolChoice.tool) ? rawToolChoice.tool.name : undefined),
);
return name ? { type: 'tool', name } : undefined;
}
function parseTools(rawTools: unknown): CanonicalTool[] | undefined {
if (!Array.isArray(rawTools)) return undefined;
const tools = rawTools
.flatMap((item) => {
if (!isRecord(item)) return [];
if (asTrimmedString(item.type).toLowerCase() === 'function' && isRecord(item.function)) {
const name = asTrimmedString(item.function.name);
if (!name) return [];
return [{
name,
...(asTrimmedString(item.function.description)
? { description: asTrimmedString(item.function.description) }
: {}),
...(isRecord(item.function.parameters) ? { inputSchema: cloneJsonValue(item.function.parameters) } : {}),
}];
}
if (Array.isArray(item.functionDeclarations)) {
return item.functionDeclarations.flatMap((declaration) => {
if (!isRecord(declaration)) return [];
const name = asTrimmedString(declaration.name);
if (!name) return [];
return [{
name,
...(asTrimmedString(declaration.description)
? { description: asTrimmedString(declaration.description) }
: {}),
...(isRecord(declaration.parametersJsonSchema)
? { inputSchema: cloneJsonValue(declaration.parametersJsonSchema) }
: (isRecord(declaration.parameters) ? { inputSchema: cloneJsonValue(declaration.parameters) } : {})),
}];
});
}
return [];
});
return tools.length > 0 ? tools : undefined;
}
export function canonicalRequestFromOpenAiBody(
input: CanonicalRequestFromOpenAiBodyInput,
): CanonicalRequestEnvelope {
const body = input.body;
const messages: CanonicalMessage[] = [];
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
for (const rawMessage of rawMessages) {
if (!isRecord(rawMessage)) continue;
const role = normalizeRole(rawMessage.role);
if (role === 'tool') {
const toolCallId = asTrimmedString(rawMessage.tool_call_id ?? rawMessage.id);
const resultText = typeof rawMessage.content === 'string'
? rawMessage.content
: safeJsonStringify(rawMessage.content ?? '');
messages.push({
role: 'tool',
parts: [{
type: 'tool_result',
toolCallId: toolCallId || 'tool',
...(resultText ? { resultText } : {}),
}],
});
continue;
}
const parts = openAiContentToCanonicalParts(rawMessage.content);
const toolCalls = Array.isArray(rawMessage.tool_calls) ? rawMessage.tool_calls : [];
for (const toolCall of toolCalls) {
if (!isRecord(toolCall)) continue;
const fn = isRecord(toolCall.function) ? toolCall.function : {};
const id = asTrimmedString(toolCall.id);
const name = asTrimmedString(toolCall.name ?? fn.name);
const argumentsJson = typeof fn.arguments === 'string'
? fn.arguments
: safeJsonStringify(fn.arguments ?? toolCall.arguments ?? {});
if (!name) continue;
parts.push({
type: 'tool_call',
id: id || `tool_${parts.length}`,
name,
argumentsJson,
});
}
messages.push({
role,
parts,
});
}
const reasoningResult = normalizeCanonicalReasoningRequest({
include: body.include,
reasoning: body.reasoning,
reasoning_effort: body.reasoning_effort,
reasoning_budget: body.reasoning_budget,
reasoning_summary: body.reasoning_summary,
});
const continuation: CanonicalContinuation = {
...(normalizeCanonicalContinuation(input.continuation) ?? {}),
...(asTrimmedString(body.previous_response_id)
? { previousResponseId: asTrimmedString(body.previous_response_id) }
: {}),
...(asTrimmedString(body.prompt_cache_key)
? { promptCacheKey: asTrimmedString(body.prompt_cache_key) }
: {}),
};
return createCanonicalRequestEnvelope({
operation: input.operation ?? 'generate',
surface: input.surface,
cliProfile: input.cliProfile ?? 'generic',
requestedModel: asTrimmedString(body.model),
stream: body.stream === true,
messages,
...(reasoningResult.reasoning ? { reasoning: reasoningResult.reasoning } : {}),
...(parseTools(body.tools) ? { tools: parseTools(body.tools) } : {}),
...(parseToolChoice(body.tool_choice) !== undefined ? { toolChoice: parseToolChoice(body.tool_choice) } : {}),
...(Object.keys(continuation).length > 0 ? { continuation } : {}),
...(input.metadata ? { metadata: input.metadata } : {}),
...((reasoningResult.metadata || input.passthrough)
? {
passthrough: {
...(input.passthrough ?? {}),
...(reasoningResult.metadata ? { transformerMetadata: reasoningResult.metadata } : {}),
},
}
: {}),
});
}
function canonicalPartsToOpenAiContent(
role: CanonicalMessageRole,
parts: CanonicalContentPart[],
): { content: string | Array<Record<string, unknown>>; reasoning?: string; toolCalls?: Array<Record<string, unknown>> } {
const contentBlocks: Array<Record<string, unknown>> = [];
const toolCalls: Array<Record<string, unknown>> = [];
const visibleText: string[] = [];
const reasoningText: string[] = [];
for (const part of parts) {
if (part.type === 'text') {
if (part.thought === true) {
reasoningText.push(part.text);
} else {
visibleText.push(part.text);
}
continue;
}
if (part.type === 'image') {
const url = asTrimmedString(part.url ?? part.dataUrl);
if (url) {
contentBlocks.push({
type: 'image_url',
image_url: { url },
});
}
continue;
}
if (part.type === 'file') {
const normalizedFile = canonicalAttachmentToNormalizedInputFile({
kind: 'file',
...(part.fileId ? { fileId: part.fileId } : {}),
...(part.fileUrl ? { fileUrl: part.fileUrl } : {}),
...(part.fileData ? { fileData: part.fileData } : {}),
...(part.filename ? { filename: part.filename } : {}),
...(part.mimeType !== undefined ? { mimeType: part.mimeType } : {}),
});
contentBlocks.push(toOpenAiChatFileBlock(normalizedFile));
continue;
}
if (part.type === 'tool_call') {
toolCalls.push({
id: part.id,
type: 'function',
function: {
name: part.name,
arguments: part.argumentsJson,
},
});
continue;
}
if (part.type === 'tool_result' && role !== 'tool') {
const text = part.resultText ?? safeJsonStringify(part.resultJson ?? '');
if (text) {
visibleText.push(text);
}
}
}
if (contentBlocks.length <= 0) {
return {
content: visibleText.join(''),
...(reasoningText.length > 0 ? { reasoning: reasoningText.join('') } : {}),
...(toolCalls.length > 0 ? { toolCalls } : {}),
};
}
if (visibleText.length > 0) {
contentBlocks.unshift({
type: 'text',
text: visibleText.join(''),
});
}
return {
content: contentBlocks,
...(reasoningText.length > 0 ? { reasoning: reasoningText.join('') } : {}),
...(toolCalls.length > 0 ? { toolCalls } : {}),
};
}
function canonicalToolChoiceToOpenAi(toolChoice: CanonicalToolChoice | undefined): unknown {
if (!toolChoice) return undefined;
if (toolChoice === 'auto' || toolChoice === 'none') return toolChoice;
if (toolChoice === 'required') return 'required';
return {
type: 'function',
function: {
name: toolChoice.name,
},
};
}
export function canonicalRequestToOpenAiChatBody(
request: CanonicalRequestEnvelope,
): Record<string, unknown> {
const messages: Array<Record<string, unknown>> = [];
for (const message of request.messages) {
if (message.role === 'tool') {
for (const part of message.parts) {
if (part.type !== 'tool_result') continue;
messages.push({
role: 'tool',
tool_call_id: part.toolCallId,
content: part.resultText ?? safeJsonStringify(part.resultJson ?? ''),
});
}
continue;
}
const converted = canonicalPartsToOpenAiContent(message.role, message.parts);
const nextMessage: Record<string, unknown> = {
role: message.role,
content: converted.content,
};
if (message.role === 'assistant' && converted.reasoning) {
nextMessage.reasoning_content = converted.reasoning;
}
if (message.role === 'assistant' && converted.toolCalls && converted.toolCalls.length > 0) {
nextMessage.tool_calls = converted.toolCalls;
if (typeof nextMessage.content !== 'string' && (nextMessage.content as Array<unknown>).length <= 0) {
nextMessage.content = '';
}
}
messages.push(nextMessage);
}
const body: Record<string, unknown> = {
model: request.requestedModel,
stream: request.stream,
messages,
};
if (request.reasoning?.effort) body.reasoning_effort = request.reasoning.effort;
if (request.reasoning?.budgetTokens !== undefined) body.reasoning_budget = request.reasoning.budgetTokens;
if (request.reasoning?.summary) body.reasoning_summary = request.reasoning.summary;
if (request.continuation?.promptCacheKey) body.prompt_cache_key = request.continuation.promptCacheKey;
if (request.continuation?.previousResponseId) body.previous_response_id = request.continuation.previousResponseId;
if (Array.isArray(request.tools) && request.tools.length > 0) {
body.tools = request.tools.map((tool) => ({
type: 'function',
function: {
name: tool.name,
...(tool.description ? { description: tool.description } : {}),
parameters: cloneJsonValue(tool.inputSchema ?? { type: 'object' }),
},
}));
}
const toolChoice = canonicalToolChoiceToOpenAi(request.toolChoice);
if (toolChoice !== undefined) body.tool_choice = toolChoice;
if (isRecord(request.passthrough)) {
for (const [key, value] of Object.entries(request.passthrough)) {
if (key === 'transformerMetadata' || body[key] !== undefined) continue;
body[key] = cloneJsonValue(value);
}
}
return body;
}
export * from './attachments.js';
export * from './reasoning.js';
export * from './tools.js';
export * from './types.js';
@@ -0,0 +1,14 @@
export type CanonicalTool = {
name: string;
description?: string;
inputSchema?: Record<string, unknown> | null;
};
export type CanonicalToolChoice =
| 'auto'
| 'none'
| 'required'
| {
type: 'tool';
name: string;
};
+108
View File
@@ -0,0 +1,108 @@
import type { CanonicalTool, CanonicalToolChoice } from './tools.js';
export type CanonicalOperation =
| 'generate'
| 'count_tokens';
export type CanonicalSurface =
| 'openai-chat'
| 'openai-responses'
| 'anthropic-messages'
| 'gemini-generate-content';
export type CanonicalCliProfile =
| 'generic'
| 'codex'
| 'claude_code'
| 'gemini_cli';
export type CanonicalMessageRole =
| 'system'
| 'developer'
| 'user'
| 'assistant'
| 'tool';
export type CanonicalTextPart = {
type: 'text';
text: string;
thought?: boolean;
};
export type CanonicalImagePart = {
type: 'image';
dataUrl?: string;
url?: string;
mimeType?: string | null;
};
export type CanonicalFilePart = {
type: 'file';
fileId?: string;
fileUrl?: string;
fileData?: string;
mimeType?: string | null;
filename?: string;
};
export type CanonicalToolCallPart = {
type: 'tool_call';
id: string;
name: string;
argumentsJson: string;
};
export type CanonicalToolResultPart = {
type: 'tool_result';
toolCallId: string;
resultText?: string;
resultJson?: unknown;
};
export type CanonicalContentPart =
| CanonicalTextPart
| CanonicalImagePart
| CanonicalFilePart
| CanonicalToolCallPart
| CanonicalToolResultPart;
export type CanonicalMessage = {
role: CanonicalMessageRole;
parts: CanonicalContentPart[];
};
export type CanonicalReasoningEffort =
| 'none'
| 'low'
| 'medium'
| 'high'
| 'max';
export type CanonicalReasoningRequest = {
effort?: CanonicalReasoningEffort;
budgetTokens?: number;
summary?: string;
includeEncryptedContent?: boolean;
};
export type CanonicalContinuation = {
sessionId?: string;
previousResponseId?: string;
promptCacheKey?: string;
turnState?: string;
};
export type CanonicalRequestEnvelope = {
operation: CanonicalOperation;
surface: CanonicalSurface;
cliProfile: CanonicalCliProfile;
requestedModel: string;
stream: boolean;
messages: CanonicalMessage[];
reasoning?: CanonicalReasoningRequest;
tools?: CanonicalTool[];
toolChoice?: CanonicalToolChoice;
continuation?: CanonicalContinuation;
metadata?: Record<string, unknown>;
passthrough?: Record<string, unknown>;
};