refactor: 将 codex provider 转换工具迁移至 @ant/model-provider 包

将纯转换工具(callIds、modelMapping、convertMessages、convertTools)
从 src/services/api/codex/ 迁移到 packages/@ant/model-provider/src/providers/codex/,
与 OpenAI/Gemini/Grok provider 保持一致的代码组织模式。同时修复了
streaming.test.ts 中缺失的 mock 导出(logAntError、context 常量、langfuse 导出)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-26 15:35:54 +08:00
parent 7d4b27c01a
commit 00cf974a4b
10 changed files with 65 additions and 21 deletions

View File

@@ -61,3 +61,10 @@ export { anthropicMessagesToOpenAI } from './shared/openaiConvertMessages.js'
export type { ConvertMessagesOptions } from './shared/openaiConvertMessages.js' export type { ConvertMessagesOptions } from './shared/openaiConvertMessages.js'
export { anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI } from './shared/openaiConvertTools.js' export { anthropicToolsToOpenAI, anthropicToolChoiceToOpenAI } from './shared/openaiConvertTools.js'
export { adaptOpenAIStreamToAnthropic } from './shared/openaiStreamAdapter.js' export { adaptOpenAIStreamToAnthropic } from './shared/openaiStreamAdapter.js'
// Codex provider utilities
export { normalizeCodexCallId, resolveCodexCallId, createCodexFallbackCallId } from './providers/codex/callIds.js'
export { resolveCodexModel, resolveCodexMaxTokens } from './providers/codex/modelMapping.js'
export { anthropicMessagesToCodexInput } from './providers/codex/convertMessages.js'
export type { CodexImageConversionOptions } from './providers/codex/convertMessages.js'
export { anthropicToolsToCodex } from './providers/codex/convertTools.js'

View File

@@ -4,7 +4,7 @@ import type {
ResponseInputItem, ResponseInputItem,
ResponseInputText, ResponseInputText,
} from 'openai/resources/responses/responses.mjs' } from 'openai/resources/responses/responses.mjs'
import type { Message } from '../../../types/message.js' import type { Message } from '../../types/index.js'
import { import {
normalizeCodexCallId, normalizeCodexCallId,
resolveCodexCallId, resolveCodexCallId,

View File

@@ -1,7 +1,6 @@
import { describe, expect, test } from 'bun:test' import { describe, expect, test } from 'bun:test'
import { createAssistantMessage, createUserMessage } from '../../../../utils/messages.js' import { createAssistantMessage, createUserMessage } from '../../../../utils/messages.js'
import { anthropicMessagesToCodexInput } from '../convertMessages.js' import { anthropicMessagesToCodexInput, anthropicToolsToCodex } from '@ant/model-provider'
import { anthropicToolsToCodex } from '../convertTools.js'
describe('anthropicMessagesToCodexInput', () => { describe('anthropicMessagesToCodexInput', () => {
test('replays assistant tool calls and user tool results in order', async () => { test('replays assistant tool calls and user tool results in order', async () => {

View File

@@ -97,33 +97,68 @@ mock.module('../client.js', () => ({
}), }),
})) }))
mock.module('../convertMessages.js', () => ({ // Mock only model resolution — conversion functions can use real implementations
anthropicMessagesToCodexInput: () => [], // since the client mock controls API responses.
})) mock.module('@ant/model-provider', () => {
// Import the real module to preserve conversion functions
mock.module('../convertTools.js', () => ({ const real = require('@ant/model-provider')
anthropicToolsToCodex: () => [], return {
})) ...real,
resolveCodexModel: () => 'gpt-5.4',
mock.module('../model.js', () => ({ resolveCodexMaxTokens: () => 4096,
resolveCodexModel: () => 'gpt-5.4', }
resolveCodexMaxTokens: () => 4096, })
}))
mock.module('../../../../utils/context.js', () => ({ mock.module('../../../../utils/context.js', () => ({
MODEL_CONTEXT_WINDOW_DEFAULT: 200_000,
COMPACT_MAX_OUTPUT_TOKENS: 20_000,
CAPPED_DEFAULT_MAX_TOKENS: 8_000,
ESCALATED_MAX_TOKENS: 64_000,
is1mContextDisabled: () => false,
has1mContext: () => false,
modelSupports1M: () => false,
getContextWindowForModel: () => 200_000,
getSonnet1mExpTreatmentEnabled: () => false,
calculateContextPercentages: () => ({}),
getModelMaxOutputTokens: () => ({ upperLimit: 4096 }), getModelMaxOutputTokens: () => ({ upperLimit: 4096 }),
getMaxThinkingTokensForModel: () => 0,
})) }))
mock.module('../../../../utils/api.js', () => ({ mock.module('../../../../utils/api.js', () => ({
toolToAPISchema: async () => ({}), toolToAPISchema: async () => ({}),
appendSystemContext: () => {},
prependUserContext: () => {},
logAPIPrefix: () => {},
splitSysPromptPrefix: () => ({ prefix: '', rest: [] }),
logContextMetrics: async () => {},
normalizeToolInput: (input: any) => input,
normalizeToolInputForAPI: (input: any) => input,
})) }))
mock.module('../../../../utils/debug.js', () => ({ mock.module('src/utils/debug.ts', () => ({
getMinDebugLogLevel: () => 'debug' as const,
isDebugMode: () => false,
enableDebugLogging: () => false,
getDebugFilter: () => null,
isDebugToStdErr: () => false,
getDebugFilePath: () => null as string | null,
setHasFormattedOutput: () => {},
getHasFormattedOutput: () => false,
flushDebugLogs: async () => {},
logForDebugging: () => {}, logForDebugging: () => {},
getDebugLogPath: () => '/tmp/mock-debug.log',
logAntError: () => {},
})) }))
mock.module('../../../../services/langfuse/tracing.js', () => ({ mock.module('../../../../services/langfuse/tracing.js', () => ({
createTrace: () => null,
recordLLMObservation: () => {}, recordLLMObservation: () => {},
recordToolObservation: () => {},
createToolBatchSpan: () => null,
endToolBatchSpan: () => {},
createSubagentTrace: () => null,
createChildSpan: () => null,
endTrace: () => {},
})) }))
mock.module('../../../../services/langfuse/convert.js', () => ({ mock.module('../../../../services/langfuse/convert.js', () => ({

View File

@@ -27,15 +27,18 @@ import {
convertOutputToLangfuse, convertOutputToLangfuse,
convertToolsToLangfuse, convertToolsToLangfuse,
} from '../../../services/langfuse/convert.js' } from '../../../services/langfuse/convert.js'
import { anthropicMessagesToCodexInput } from './convertMessages.js' import {
import { anthropicToolsToCodex } from './convertTools.js' anthropicMessagesToCodexInput,
anthropicToolsToCodex,
resolveCodexMaxTokens,
resolveCodexModel,
} from '@ant/model-provider'
import { getCodexClient } from './client.js' import { getCodexClient } from './client.js'
import { uploadCodexBase64Image } from './imageUpload.js' import { uploadCodexBase64Image } from './imageUpload.js'
import { import {
getCodexConfigurationError, getCodexConfigurationError,
normalizeCodexError, normalizeCodexError,
} from './errors.js' } from './errors.js'
import { resolveCodexMaxTokens, resolveCodexModel } from './model.js'
import { sanitizeCodexRequest } from './preflight.js' import { sanitizeCodexRequest } from './preflight.js'
import { import {
addCodexUsage, addCodexUsage,

View File

@@ -4,7 +4,7 @@ import type {
ResponseInputItem, ResponseInputItem,
Tool, Tool,
} from 'openai/resources/responses/responses.mjs' } from 'openai/resources/responses/responses.mjs'
import { normalizeCodexCallId } from './callIds.js' import { normalizeCodexCallId } from '@ant/model-provider'
function isRecord(value: unknown): value is Record<string, unknown> { function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value) return typeof value === 'object' && value !== null && !Array.isArray(value)

View File

@@ -14,7 +14,7 @@ import {
normalizeContentFromAPI, normalizeContentFromAPI,
} from '../../../utils/messages.js' } from '../../../utils/messages.js'
import { getCodexClient } from './client.js' import { getCodexClient } from './client.js'
import { resolveCodexCallId } from './callIds.js' import { resolveCodexCallId } from '@ant/model-provider'
import { toStreamingCodexRequest } from './preflight.js' import { toStreamingCodexRequest } from './preflight.js'
export type RawAssistantBlock = export type RawAssistantBlock =