mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
* refactor: 创建 @anthropic-ai/model-provider 包骨架与类型定义
- 新建 workspace 包 packages/@anthropic-ai/model-provider
- 定义 ModelProviderHooks 接口(依赖注入:分析、成本、日志等)
- 定义 ClientFactories 接口(Anthropic/OpenAI/Gemini/Grok 客户端工厂)
- 搬入核心类型:Message 体系、NonNullableUsage、EMPTY_USAGE、SystemPrompt、错误常量
- 主项目 src/types/message.ts 等改为 re-export,保持向后兼容
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: 提升 OpenAI 转换器和模型映射到 model-provider 包
- 搬入 OpenAI 消息转换(convertMessages)、工具转换(convertTools)、流适配(streamAdapter)
- 搬入 OpenAI 和 Grok 模型映射(resolveOpenAIModel、resolveGrokModel)
- 主项目文件改为 thin re-export proxy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: 搬入 Gemini 兼容层到 model-provider 包
- 搬入 Gemini 类型定义、消息转换、工具转换、流适配、模型映射
- 主项目 gemini/ 目录下文件改为 thin re-export proxy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: 搬入 errorUtils 并迁移消费者导入到 model-provider
- 搬入 formatAPIError、extractConnectionErrorDetails 等 errorUtils
- 迁移 10 个消费者文件直接从 @anthropic-ai/model-provider 导入
- 更新 emptyUsage、sdkUtilityTypes、systemPromptType 为 re-export proxy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: compact 模型降级为 -1 模式(Opus→Sonnet, Sonnet→Haiku)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: 添加 agent-loop 绘图
* Revert "feat: compact 模型降级为 -1 模式(Opus→Sonnet, Sonnet→Haiku)"
This reverts commit e458d6391d.
* docs: 添加简化版 agent loop
* fix: 修复 n 快捷键导致关闭的问题
* fix: 修复 node 下 ws 没打包问题
* docs: 修复链接
* test: 添加测试支持
* fix: 修复类型问题(#267) (#271)
* fix: 修复 Bun 的 polyfill 问题
* fix: 类型修复完成
* feat: 统一所有包的类型文件
* fix: 修复构建问题
* test: 修复类型校验 (#279)
* fix: 修复 Bun 的 polyfill 问题
* fix: 类型修复完成
* feat: 统一所有包的类型文件
* fix: 修复构建问题
* fix(remote-control): harden self-hosted session flows (#278)
Co-authored-by: chengzifeng <chengzifeng@meituan.com>
* docs: update contributors
* build: 新增 vite 构建流程
* feat: 添加环境变量支持以覆盖 max_tokens 设置
* feat(langfuse): LLM generation 记录工具定义
将 Anthropic 格式的工具定义转换为 Langfuse 兼容的 OpenAI 格式,
并在 generation 的 input 中以 { messages, tools } 结构传入,
以便在 Langfuse UI 中查看完整的工具定义信息。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: 添加对 ACP 协议的支持 (#284)
* feat: 适配 zed acp 协议
* docs: 完善 acp 文档
* chore: 1.4.0
* conflict: 解决冲突
* feat: 添加测试覆盖率上报
* style: 改名加移动文件夹位置
* refactor: 移动测试用例及实现
* test: 修复测试用例完成
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Cheng Zi Feng <1154238323@qq.com>
Co-authored-by: chengzifeng <chengzifeng@meituan.com>
Co-authored-by: claude-code-best <272536312+claude-code-best@users.noreply.github.com>
124 lines
4.1 KiB
TypeScript
124 lines
4.1 KiB
TypeScript
import type { BetaToolUnion } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
|
import type { ChatCompletionTool } from 'openai/resources/chat/completions/completions.mjs'
|
|
|
|
/**
|
|
* Convert Anthropic tool schemas to OpenAI function calling format.
|
|
*
|
|
* Anthropic: { name, description, input_schema }
|
|
* OpenAI: { type: "function", function: { name, description, parameters } }
|
|
*
|
|
* Anthropic-specific fields (cache_control, defer_loading, etc.) are stripped.
|
|
*/
|
|
export function anthropicToolsToOpenAI(
|
|
tools: BetaToolUnion[],
|
|
): ChatCompletionTool[] {
|
|
return tools
|
|
.filter(tool => {
|
|
// Only convert standard tools (skip server tools like computer_use, etc.)
|
|
const toolType = (tool as unknown as { type?: string }).type
|
|
return tool.type === 'custom' || !('type' in tool) || toolType !== 'server'
|
|
})
|
|
.map(tool => {
|
|
// Handle the various tool shapes from Anthropic SDK
|
|
const anyTool = tool as unknown as Record<string, unknown>
|
|
const name = (anyTool.name as string) || ''
|
|
const description = (anyTool.description as string) || ''
|
|
const inputSchema = anyTool.input_schema as Record<string, unknown> | undefined
|
|
|
|
return {
|
|
type: 'function' as const,
|
|
function: {
|
|
name,
|
|
description,
|
|
parameters: sanitizeJsonSchema(inputSchema || { type: 'object', properties: {} }),
|
|
},
|
|
} satisfies ChatCompletionTool
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Recursively sanitize a JSON Schema for OpenAI-compatible providers.
|
|
*
|
|
* Many OpenAI-compatible endpoints (Ollama, DeepSeek, vLLM, etc.) do not
|
|
* support the `const` keyword in JSON Schema. Convert it to `enum` with a
|
|
* single-element array, which is semantically equivalent.
|
|
*/
|
|
function sanitizeJsonSchema(schema: Record<string, unknown>): Record<string, unknown> {
|
|
if (!schema || typeof schema !== 'object') return schema
|
|
|
|
const result = { ...schema }
|
|
|
|
// Convert `const` → `enum: [value]`
|
|
if ('const' in result) {
|
|
result.enum = [result.const]
|
|
delete result.const
|
|
}
|
|
|
|
// Recursively process nested schemas
|
|
const objectKeys = ['properties', 'definitions', '$defs', 'patternProperties'] as const
|
|
for (const key of objectKeys) {
|
|
const nested = result[key]
|
|
if (nested && typeof nested === 'object') {
|
|
const sanitized: Record<string, unknown> = {}
|
|
for (const [k, v] of Object.entries(nested as Record<string, unknown>)) {
|
|
sanitized[k] = v && typeof v === 'object' ? sanitizeJsonSchema(v as Record<string, unknown>) : v
|
|
}
|
|
result[key] = sanitized
|
|
}
|
|
}
|
|
|
|
// Recursively process single-schema keys
|
|
const singleKeys = ['items', 'additionalProperties', 'not', 'if', 'then', 'else', 'contains', 'propertyNames'] as const
|
|
for (const key of singleKeys) {
|
|
const nested = result[key]
|
|
if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
|
|
result[key] = sanitizeJsonSchema(nested as Record<string, unknown>)
|
|
}
|
|
}
|
|
|
|
// Recursively process array-of-schemas keys
|
|
const arrayKeys = ['anyOf', 'oneOf', 'allOf'] as const
|
|
for (const key of arrayKeys) {
|
|
const nested = result[key]
|
|
if (Array.isArray(nested)) {
|
|
result[key] = nested.map(item =>
|
|
item && typeof item === 'object' ? sanitizeJsonSchema(item as Record<string, unknown>) : item
|
|
)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* Map Anthropic tool_choice to OpenAI tool_choice format.
|
|
*
|
|
* Anthropic → OpenAI:
|
|
* - { type: "auto" } → "auto"
|
|
* - { type: "any" } → "required"
|
|
* - { type: "tool", name } → { type: "function", function: { name } }
|
|
* - undefined → undefined (use provider default)
|
|
*/
|
|
export function anthropicToolChoiceToOpenAI(
|
|
toolChoice: unknown,
|
|
): string | { type: 'function'; function: { name: string } } | undefined {
|
|
if (!toolChoice || typeof toolChoice !== 'object') return undefined
|
|
|
|
const tc = toolChoice as Record<string, unknown>
|
|
const type = tc.type as string
|
|
|
|
switch (type) {
|
|
case 'auto':
|
|
return 'auto'
|
|
case 'any':
|
|
return 'required'
|
|
case 'tool':
|
|
return {
|
|
type: 'function',
|
|
function: { name: tc.name as string },
|
|
}
|
|
default:
|
|
return undefined
|
|
}
|
|
}
|