mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 05:45:51 +00:00
* fix: reorder tool and user messages for OpenAI API compatibility (#168) Fixes #168 OpenAI requires that an assistant message with tool_calls be immediately followed by tool messages. Previously, convertInternalUserMessage output user content before tool results, causing 400 errors. Now tool messages are pushed first. * fix: 修复OpenAI兼容层中deferred tools处理问题 提交描述: 修复了在使用OpenAI兼容API时TaskCreate工具调用失败的问题。 问题: - 当使用OpenAI兼容API模型时,调用TaskCreate工具出现"InputValidationError: The required parameter `subject` is missing"错误 - OpenAI兼容层没有正确处理deferred tools的过滤逻辑,导致工具schema没有被正确发送给模型 修复: 1. 在OpenAI兼容层中添加了与Anthropic API路径一致的deferred tools处理逻辑 2. 导入必要的工具搜索相关函数: isToolSearchEnabled, extractDiscoveredToolNames, isDeferredTool等 3. 实现工具过滤逻辑: - 检查工具搜索是否启用 - 构建deferred tools集合 - 过滤工具列表: 只包含非deferred工具或已发现的deferred工具 - 为deferred tools设置deferLoading标志 4. 修正了extractDiscoveredToolNames函数的导入路径错误 影响: - 解决了TaskCreate工具调用时的参数验证错误 - 确保OpenAI兼容层与Anthropic API路径在处理deferred tools时行为一致 - 支持工具搜索功能在OpenAI兼容模式下正常工作 修改的文件: - src/services/api/openai/index.ts - 主要修复文件 测试建议: 1. 使用OpenAI兼容API模型时,TaskCreate工具应该可以正常调用 2. 如果工具搜索功能启用,可能需要先使用ToolSearchTool来发现TaskCreate工具 3. 验证工具调用时不再出现"InputValidationError"错误 这个修复确保了当使用OpenAI兼容API(如Ollama、DeepSeek、vLLM等)时,deferred tools(如TaskCreate)能够被正确处理,解决了工具调用失败的问题。 * fix: 更新未发送工具架构提示,提供OpenAI兼容模型的使用步骤 * fix: Handle undefined command names in getCommandName function - Modified getCommandName in src/types/command.ts to return empty string instead of undefined when cmd.name is undefined - Added null checks in src/hooks/useTypeahead.tsx to safely handle command names - Prevents "undefined is not an object" error when FEATURE_BUDDY=1 and FEATURE_FORK_SUBAGENT=1 are enabled The error occurred because getCommandName(cmd) could return undefined when cmd.name was undefined, causing .length access to fail.
218 lines
7.6 KiB
TypeScript
218 lines
7.6 KiB
TypeScript
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
|
|
import type { UUID } from 'crypto'
|
|
import type { CanUseToolFn } from '../hooks/useCanUseTool.js'
|
|
import type { CompactionResult } from '../services/compact/compact.js'
|
|
import type { ScopedMcpServerConfig } from '../services/mcp/types.js'
|
|
import type { ToolUseContext } from '../Tool.js'
|
|
import type { EffortValue } from '../utils/effort.js'
|
|
import type { IDEExtensionInstallationStatus, IdeType } from '../utils/ide.js'
|
|
import type { SettingSource } from '../utils/settings/constants.js'
|
|
import type { HooksSettings } from '../utils/settings/types.js'
|
|
import type { ThemeName } from '../utils/theme.js'
|
|
import type { LogOption } from './logs.js'
|
|
import type { Message } from './message.js'
|
|
import type { PluginManifest } from './plugin.js'
|
|
|
|
export type LocalCommandResult =
|
|
| { type: 'text'; value: string }
|
|
| {
|
|
type: 'compact'
|
|
compactionResult: CompactionResult
|
|
displayText?: string
|
|
}
|
|
| { type: 'skip' } // Skip messages
|
|
|
|
export type PromptCommand = {
|
|
type: 'prompt'
|
|
progressMessage: string
|
|
contentLength: number // Length of command content in characters (used for token estimation)
|
|
argNames?: string[]
|
|
allowedTools?: string[]
|
|
model?: string
|
|
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled'
|
|
pluginInfo?: {
|
|
pluginManifest: PluginManifest
|
|
repository: string
|
|
}
|
|
disableNonInteractive?: boolean
|
|
// Hooks to register when this skill is invoked
|
|
hooks?: HooksSettings
|
|
// Base directory for skill resources (used to set CLAUDE_PLUGIN_ROOT environment variable for skill hooks)
|
|
skillRoot?: string
|
|
// Execution context: 'inline' (default) or 'fork' (run as sub-agent)
|
|
// 'inline' = skill content expands into the current conversation
|
|
// 'fork' = skill runs in a sub-agent with separate context and token budget
|
|
context?: 'inline' | 'fork'
|
|
// Agent type to use when forked (e.g., 'Bash', 'general-purpose')
|
|
// Only applicable when context is 'fork'
|
|
agent?: string
|
|
effort?: EffortValue
|
|
// Glob patterns for file paths this skill applies to
|
|
// When set, the skill is only visible after the model touches matching files
|
|
paths?: string[]
|
|
getPromptForCommand(
|
|
args: string,
|
|
context: ToolUseContext,
|
|
): Promise<ContentBlockParam[]>
|
|
}
|
|
|
|
/**
|
|
* The call signature for a local command implementation.
|
|
*/
|
|
export type LocalCommandCall = (
|
|
args: string,
|
|
context: LocalJSXCommandContext,
|
|
) => Promise<LocalCommandResult>
|
|
|
|
/**
|
|
* Module shape returned by load() for lazy-loaded local commands.
|
|
*/
|
|
export type LocalCommandModule = {
|
|
call: LocalCommandCall
|
|
}
|
|
|
|
type LocalCommand = {
|
|
type: 'local'
|
|
supportsNonInteractive: boolean
|
|
load: () => Promise<LocalCommandModule>
|
|
}
|
|
|
|
export type LocalJSXCommandContext = ToolUseContext & {
|
|
canUseTool?: CanUseToolFn
|
|
setMessages: (updater: (prev: Message[]) => Message[]) => void
|
|
options: {
|
|
dynamicMcpConfig?: Record<string, ScopedMcpServerConfig>
|
|
ideInstallationStatus: IDEExtensionInstallationStatus | null
|
|
theme: ThemeName
|
|
}
|
|
onChangeAPIKey: () => void
|
|
onChangeDynamicMcpConfig?: (
|
|
config: Record<string, ScopedMcpServerConfig>,
|
|
) => void
|
|
onInstallIDEExtension?: (ide: IdeType) => void
|
|
resume?: (
|
|
sessionId: UUID,
|
|
log: LogOption,
|
|
entrypoint: ResumeEntrypoint,
|
|
) => Promise<void>
|
|
}
|
|
|
|
export type ResumeEntrypoint =
|
|
| 'cli_flag'
|
|
| 'slash_command_picker'
|
|
| 'slash_command_session_id'
|
|
| 'slash_command_title'
|
|
| 'fork'
|
|
|
|
export type CommandResultDisplay = 'skip' | 'system' | 'user'
|
|
|
|
/**
|
|
* Callback when a command completes.
|
|
* @param result - Optional user-visible message to display
|
|
* @param options - Optional configuration for command completion
|
|
* @param options.display - How to display the result: 'skip' | 'system' | 'user' (default)
|
|
* @param options.shouldQuery - If true, send messages to the model after command completes
|
|
* @param options.metaMessages - Additional messages to insert as isMeta (model-visible but hidden)
|
|
*/
|
|
export type LocalJSXCommandOnDone = (
|
|
result?: string,
|
|
options?: {
|
|
display?: CommandResultDisplay
|
|
shouldQuery?: boolean
|
|
metaMessages?: string[]
|
|
nextInput?: string
|
|
submitNextInput?: boolean
|
|
},
|
|
) => void
|
|
|
|
/**
|
|
* The call signature for a local JSX command implementation.
|
|
*/
|
|
export type LocalJSXCommandCall = (
|
|
onDone: LocalJSXCommandOnDone,
|
|
context: ToolUseContext & LocalJSXCommandContext,
|
|
args: string,
|
|
) => Promise<React.ReactNode>
|
|
|
|
/**
|
|
* Module shape returned by load() for lazy-loaded commands.
|
|
*/
|
|
export type LocalJSXCommandModule = {
|
|
call: LocalJSXCommandCall
|
|
}
|
|
|
|
type LocalJSXCommand = {
|
|
type: 'local-jsx'
|
|
/**
|
|
* Lazy-load the command implementation.
|
|
* Returns a module with a call() function.
|
|
* This defers loading heavy dependencies until the command is invoked.
|
|
*/
|
|
load: () => Promise<LocalJSXCommandModule>
|
|
}
|
|
|
|
/**
|
|
* Declares which auth/provider environments a command is available in.
|
|
*
|
|
* This is separate from `isEnabled()`:
|
|
* - `availability` = who can use this (auth/provider requirement, static)
|
|
* - `isEnabled()` = is this turned on right now (GrowthBook, platform, env vars)
|
|
*
|
|
* Commands without `availability` are available everywhere.
|
|
* Commands with `availability` are only shown if the user matches at least one
|
|
* of the listed auth types. See meetsAvailabilityRequirement() in commands.ts.
|
|
*
|
|
* Example: `availability: ['claude-ai', 'console']` shows the command to
|
|
* claude.ai subscribers and direct Console API key users (api.anthropic.com),
|
|
* but hides it from Bedrock/Vertex/Foundry users and custom base URL users.
|
|
*/
|
|
export type CommandAvailability =
|
|
// claude.ai OAuth subscriber (Pro/Max/Team/Enterprise via claude.ai)
|
|
| 'claude-ai'
|
|
// Console API key user (direct api.anthropic.com, not via claude.ai OAuth)
|
|
| 'console'
|
|
|
|
export type CommandBase = {
|
|
availability?: CommandAvailability[]
|
|
description: string
|
|
hasUserSpecifiedDescription?: boolean
|
|
/** Defaults to true. Only set when the command has conditional enablement (feature flags, env checks, etc). */
|
|
isEnabled?: () => boolean
|
|
/** Defaults to false. Only set when the command should be hidden from typeahead/help. */
|
|
isHidden?: boolean
|
|
name: string
|
|
aliases?: string[]
|
|
isMcp?: boolean
|
|
argumentHint?: string // Hint text for command arguments (displayed in gray after command)
|
|
whenToUse?: string // From the "Skill" spec. Detailed usage scenarios for when to use this command
|
|
version?: string // Version of the command/skill
|
|
disableModelInvocation?: boolean // Whether to disable this command from being invoked by models
|
|
userInvocable?: boolean // Whether users can invoke this skill by typing /skill-name
|
|
loadedFrom?:
|
|
| 'commands_DEPRECATED'
|
|
| 'skills'
|
|
| 'plugin'
|
|
| 'managed'
|
|
| 'bundled'
|
|
| 'mcp' // Where the command was loaded from
|
|
kind?: 'workflow' // Distinguishes workflow-backed commands (badged in autocomplete)
|
|
immediate?: boolean // If true, command executes immediately without waiting for a stop point (bypasses queue)
|
|
isSensitive?: boolean // If true, args are redacted from the conversation history
|
|
/** Defaults to `name`. Only override when the displayed name differs (e.g. plugin prefix stripping). */
|
|
userFacingName?: () => string
|
|
}
|
|
|
|
export type Command = CommandBase &
|
|
(PromptCommand | LocalCommand | LocalJSXCommand)
|
|
|
|
/** Resolves the user-visible name, falling back to `cmd.name` when not overridden. */
|
|
export function getCommandName(cmd: CommandBase): string {
|
|
const name = cmd.userFacingName?.() ?? cmd.name
|
|
return name || ''
|
|
}
|
|
|
|
/** Resolves whether the command is enabled, defaulting to true. */
|
|
export function isCommandEnabled(cmd: CommandBase): boolean {
|
|
return cmd.isEnabled?.() ?? true
|
|
}
|