Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const result = await Bun.build({
splitting: true,
define: getMacroDefines(),
features,
external: ['openai'],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking all package.json files for openai dependency placement..."
fd -a '^package\.json$' | while read -r f; do
  echo "== $f =="
  jq -r '{
    name: (.name // "unknown"),
    openai_in_dependencies: (.dependencies.openai // null),
    openai_in_optionalDependencies: (.optionalDependencies.openai // null),
    openai_in_devDependencies: (.devDependencies.openai // null)
  }' "$f"
done

echo
echo "Confirming bundler externalization line:"
rg -n "external:\\s*\\[[^\\]]*openai" build.ts

Repository: claude-code-best/claude-code

Length of output: 3438


🏁 Script executed:

cat -n build.ts | head -100

Repository: claude-code-best/claude-code

Length of output: 3651


🏁 Script executed:

# Search for imports and usage of 'openai' in the codebase
rg -i "import.*openai|from\s+['\"]openai|require\(['\"]openai" --type ts --type tsx -B 2 -A 2

Repository: claude-code-best/claude-code

Length of output: 100


🏁 Script executed:

# Check what entry points/outputs are defined in build.ts
rg -A 5 -B 5 "entrypoint|entry|outdir|outfile" build.ts

Repository: claude-code-best/claude-code

Length of output: 3056


🏁 Script executed:

rg "openai" --type ts -B 2 -A 2 | head -100

Repository: claude-code-best/claude-code

Length of output: 5825


🏁 Script executed:

# Check specifically if openai is imported in cli.tsx or other runtime code
rg "from\s+['\"]openai|import.*openai" --type ts -A 3

Repository: claude-code-best/claude-code

Length of output: 4714


Move openai from devDependencies to dependencies.

Line 62 externalizes openai (preventing bundling), but openai is dynamically imported at runtime in src/services/api/openai/client.ts and src/services/api/grok/client.ts. Since it's not bundled, the CLI depends on openai being available as a production dependency. Currently it's only in devDependencies, which means production installations will fail when using OpenAI or Grok providers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build.ts` at line 62, The project currently externalizes 'openai' in build.ts
while the code (src/services/api/openai/client.ts and
src/services/api/grok/client.ts) dynamically imports it at runtime, but 'openai'
is only listed in devDependencies; update package.json to move 'openai' from
devDependencies into dependencies so production installs include it, then
reinstall/update the lockfile (npm/yarn/pnpm) to ensure the runtime package is
present; you do not need to change the external array in build.ts.

})

if (!result.success) {
Expand Down
5 changes: 3 additions & 2 deletions packages/builtin-tools/src/tools/AgentTool/AgentTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
AssistantMessage,
Message as MessageType,
NormalizedUserMessage,
UserMessage,
} from 'src/types/message.js'
import { getQuerySourceForAgent } from 'src/utils/promptCategory.js'
import { z } from 'zod/v4'
Expand Down Expand Up @@ -1464,7 +1465,7 @@ export const AgentTool = buildTool({
) {
onProgress({
toolUseID: message.toolUseID as string,
data: message.data,
data: message.data as ShellProgress,
})
}

Expand Down Expand Up @@ -1497,7 +1498,7 @@ export const AgentTool = buildTool({
onProgress({
toolUseID: `agent_${assistantMessage.message.id}`,
data: {
message: m,
message: m as AssistantMessage | UserMessage,
type: 'agent_progress',
// prompt only needed on first progress message (UI.tsx:624
// reads progressMessages[0]). Omit here to avoid duplication.
Expand Down
187 changes: 92 additions & 95 deletions packages/builtin-tools/src/tools/AgentTool/UI.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,48 @@
import type {
ContentBlock,
ToolResultBlockParam,
ToolUseBlockParam,
} from '@anthropic-ai/sdk/resources/index.mjs'
type BetaContentBlock = ContentBlock | ToolResultBlockParam
import * as React from 'react'
import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js'
import {
CtrlOToExpand,
SubAgentProvider,
} from 'src/components/CtrlOToExpand.js'
import { Byline, KeyboardShortcutHint } from '@anthropic/ink'
import type { z } from 'zod/v4'
import { AgentProgressLine } from 'src/components/AgentProgressLine.js'
import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js'
import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js'
import { Markdown } from 'src/components/Markdown.js'
import { Message as MessageComponent } from 'src/components/Message.js'
import { MessageResponse } from 'src/components/MessageResponse.js'
import { ToolUseLoader } from 'src/components/ToolUseLoader.js'
import { Box, Text } from '@anthropic/ink'
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js'
import { findToolByName, type Tools } from 'src/Tool.js'
import type { Message, ProgressMessage } from 'src/types/message.js'
import type { AgentToolProgress } from 'src/types/tools.js'
import { count } from 'src/utils/array.js'
import {
getSearchOrReadFromContent,
getSearchReadSummaryText,
} from 'src/utils/collapseReadSearch.js'
import { getDisplayPath } from 'src/utils/file.js'
import { formatDuration, formatNumber } from 'src/utils/format.js'
import {
buildSubagentLookups,
createAssistantMessage,
EMPTY_LOOKUPS,
} from 'src/utils/messages.js'
import type { ModelAlias } from 'src/utils/model/aliases.js'
import {
getMainLoopModel,
parseUserSpecifiedModel,
renderModelName,
} from 'src/utils/model/model.js'
import type { Theme, ThemeName } from 'src/utils/theme.js'
import type {
outputSchema,
Progress,
RemoteLaunchedOutput,
} from './AgentTool.js'
import { inputSchema } from './AgentTool.js'
import { getAgentColor } from './agentColorManager.js'
import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js'
import { BetaUsage } from '@anthropic-ai/sdk/resources/beta.mjs'

const MAX_PROGRESS_MESSAGES_TO_SHOW = 3
import type { ContentBlock, ToolResultBlockParam, ToolUseBlockParam } from '@anthropic-ai/sdk/resources/index.mjs';
type BetaContentBlock = ContentBlock | ToolResultBlockParam;
import * as React from 'react';
import { ConfigurableShortcutHint } from 'src/components/ConfigurableShortcutHint.js';
import { CtrlOToExpand, SubAgentProvider } from 'src/components/CtrlOToExpand.js';
import { Byline, KeyboardShortcutHint } from '@anthropic/ink';
import type { z } from 'zod/v4';
import { AgentProgressLine } from 'src/components/AgentProgressLine.js';
import { FallbackToolUseErrorMessage } from 'src/components/FallbackToolUseErrorMessage.js';
import { FallbackToolUseRejectedMessage } from 'src/components/FallbackToolUseRejectedMessage.js';
import { Markdown } from 'src/components/Markdown.js';
import { Message as MessageComponent } from 'src/components/Message.js';
import { MessageResponse } from 'src/components/MessageResponse.js';
import { ToolUseLoader } from 'src/components/ToolUseLoader.js';
import { Box, Text } from '@anthropic/ink';
import { getDumpPromptsPath } from 'src/services/api/dumpPrompts.js';
import { findToolByName, type Tools } from 'src/Tool.js';
import type { Message, MessageContent, ProgressMessage } from 'src/types/message.js';
import type { AgentToolProgress } from 'src/types/tools.js';
import { count } from 'src/utils/array.js';
import { getSearchOrReadFromContent, getSearchReadSummaryText } from 'src/utils/collapseReadSearch.js';
import { getDisplayPath } from 'src/utils/file.js';
import { formatDuration, formatNumber } from 'src/utils/format.js';
import { buildSubagentLookups, createAssistantMessage, EMPTY_LOOKUPS } from 'src/utils/messages.js';
import type { ModelAlias } from 'src/utils/model/aliases.js';
import { getMainLoopModel, parseUserSpecifiedModel, renderModelName } from 'src/utils/model/model.js';
import type { Theme, ThemeName } from 'src/utils/theme.js';
import type { outputSchema, Progress, RemoteLaunchedOutput } from './AgentTool.js';
import { inputSchema } from './AgentTool.js';
import { getAgentColor } from './agentColorManager.js';
import { GENERAL_PURPOSE_AGENT } from './built-in/generalPurposeAgent.js';
import { BetaUsage } from '@anthropic-ai/sdk/resources/beta.mjs';

const MAX_PROGRESS_MESSAGES_TO_SHOW = 3;

/** API `content` 可能是字符串或块数组;此处统一成块数组以便 `.some` / 遍历。 */
function asContentBlockArray(content: MessageContent | undefined): BetaContentBlock[] {
if (content == null) {
return [];
}
if (typeof content === 'string') {
return [];
}
return content as BetaContentBlock[];
}

/**
* Guard: checks if progress data has a `message` field (agent_progress or
Expand Down Expand Up @@ -87,12 +76,18 @@ function getSearchOrReadInfo(

// Check tool_use (assistant message)
if (message.type === 'assistant') {
return getSearchOrReadFromContent(message.message.content[0], tools)
const blocks = asContentBlockArray(message.message.content);
const first = blocks[0];
if (!first) {
return null;
}
return getSearchOrReadFromContent(first, tools);
}

// Check tool_result (user message) - find corresponding tool use from the map
if (message.type === 'user') {
const content = message.message.content[0]
const blocks = asContentBlockArray(message.message.content);
const content = blocks[0];
if (content?.type === 'tool_result') {
const toolUse = toolUseByID.get(content.tool_use_id)
if (toolUse) {
Expand Down Expand Up @@ -173,7 +168,7 @@ function processProgressMessages(
for (const msg of agentMessages) {
// Track tool_use blocks as we see them
if (msg.data.message.type === 'assistant') {
for (const c of msg.data.message.message.content) {
for (const c of asContentBlockArray(msg.data.message.message.content)) {
if (c.type === 'tool_use') {
toolUseByID.set(c.id, c as ToolUseBlockParam)
}
Expand Down Expand Up @@ -555,11 +550,11 @@ export function renderToolUseProgressMessage(
if (!hasProgressMessage(msg.data)) {
return false
}
const message = msg.data.message
return message.message.content.some(
const message = msg.data.message;
return asContentBlockArray(message.message.content).some(
(content: BetaContentBlock) => content.type === 'tool_use',
)
})
);
});

const latestAssistant = progressMessages.findLast(
(msg): msg is ProgressMessage<AgentToolProgress> =>
Expand All @@ -568,12 +563,14 @@ export function renderToolUseProgressMessage(

let tokens = null
if (latestAssistant?.data.message.type === 'assistant') {
const usage = latestAssistant.data.message.message.usage
tokens =
(usage.cache_creation_input_tokens ?? 0) +
(usage.cache_read_input_tokens ?? 0) +
usage.input_tokens +
usage.output_tokens
const usage = latestAssistant.data.message.message.usage;
if (usage) {
tokens =
Number(usage.cache_creation_input_tokens ?? 0) +
Number(usage.cache_read_input_tokens ?? 0) +
Number(usage.input_tokens ?? 0) +
Number(usage.output_tokens ?? 0);
}
}

return { toolUseCount, tokens }
Expand Down Expand Up @@ -631,10 +628,10 @@ export function renderToolUseProgressMessage(
if (!hasProgressMessage(data)) {
return false
}
return data.message.message.content.some(
return asContentBlockArray(data.message.message.content).some(
(content: BetaContentBlock) => content.type === 'tool_use',
)
})
);
});

const firstData = progressMessages[0]?.data
const prompt =
Expand Down Expand Up @@ -801,9 +798,9 @@ function calculateAgentStats(progressMessages: ProgressMessage<Progress>[]): {
const message = msg.data.message
return (
message.type === 'user' &&
message.message.content.some((content: BetaContentBlock) => content.type === 'tool_result')
)
})
asContentBlockArray(message.message.content).some((content: BetaContentBlock) => content.type === 'tool_result')
);
});

const latestAssistant = progressMessages.findLast(
(msg): msg is ProgressMessage<AgentToolProgress> =>
Expand All @@ -812,12 +809,14 @@ function calculateAgentStats(progressMessages: ProgressMessage<Progress>[]): {

let tokens = null
if (latestAssistant?.data.message.type === 'assistant') {
const usage = latestAssistant.data.message.message.usage
tokens =
(usage.cache_creation_input_tokens ?? 0) +
(usage.cache_read_input_tokens ?? 0) +
usage.input_tokens +
usage.output_tokens
const usage = latestAssistant.data.message.message.usage;
if (usage) {
tokens =
Number(usage.cache_creation_input_tokens ?? 0) +
Number(usage.cache_read_input_tokens ?? 0) +
Number(usage.input_tokens ?? 0) +
Number(usage.output_tokens ?? 0);
}
}

return { toolUseCount, tokens }
Expand Down Expand Up @@ -1036,7 +1035,7 @@ export function extractLastToolInfo(
continue
}
if (pm.data.message.type === 'assistant') {
for (const c of pm.data.message.message.content) {
for (const c of asContentBlockArray(pm.data.message.message.content)) {
if (c.type === 'tool_use') {
toolUseByID.set(c.id, c as ToolUseBlockParam)
}
Expand Down Expand Up @@ -1072,21 +1071,19 @@ export function extractLastToolInfo(
}

// Find the last tool_result message
const lastToolResult = progressMessages.findLast(
(msg): msg is ProgressMessage<AgentToolProgress> => {
if (!hasProgressMessage(msg.data)) {
return false
}
const message = msg.data.message
return (
message.type === 'user' &&
message.message.content.some((c: BetaContentBlock) => c.type === 'tool_result')
)
},
)
const lastToolResult = progressMessages.findLast((msg): msg is ProgressMessage<AgentToolProgress> => {
if (!hasProgressMessage(msg.data)) {
return false;
}
const message = msg.data.message;
return (
message.type === 'user' &&
asContentBlockArray(message.message.content).some((c: BetaContentBlock) => c.type === 'tool_result')
);
});

if (lastToolResult?.data.message.type === 'user') {
const toolResultBlock = lastToolResult.data.message.message.content.find(
const toolResultBlock = asContentBlockArray(lastToolResult.data.message.message.content).find(
(c: BetaContentBlock) => c.type === 'tool_result',
)

Expand Down
2 changes: 1 addition & 1 deletion packages/builtin-tools/src/tools/SkillTool/SkillTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ async function executeForkedSkill(
onProgress({
toolUseID: `skill_${parentMessage.message.id}`,
data: {
message: m,
message: m as AssistantMessage | UserMessage,
type: 'skill_progress',
prompt: skillContent,
agentId,
Expand Down
6 changes: 6 additions & 0 deletions src/commands/login/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import {
resetAutoModeGateCheck,
resetBypassPermissionsCheck,
} from '../../utils/permissions/bypassPermissionsKillswitch.js'
import { applyConfigEnvironmentVariables } from '../../utils/managedEnv.js'
import { resetModelStrings } from '../../utils/model/modelStrings.js'
import { resetSettingsCache } from '../../utils/settings/settingsCache.js'
import { resetUserCache } from '../../utils/user.js'

export async function call(
Expand All @@ -40,6 +43,9 @@ export async function call(
// Reset cost state when switching accounts
resetCostState()
// Refresh remotely managed settings after login (non-blocking)
resetSettingsCache()
applyConfigEnvironmentVariables()
resetModelStrings()
void refreshRemoteManagedSettings()
// Refresh policy limits after login (non-blocking)
void refreshPolicyLimits()
Expand Down
9 changes: 5 additions & 4 deletions src/services/api/grok/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import OpenAI from 'openai'
import type OpenAI from 'openai'
import { getProxyFetchOptions } from 'src/utils/proxy.js'

/**
Expand All @@ -12,17 +12,18 @@ const DEFAULT_BASE_URL = 'https://api.x.ai/v1'

let cachedClient: OpenAI | null = null

export function getGrokClient(options?: {
export async function getGrokClient(options?: {
maxRetries?: number
fetchOverride?: typeof fetch
source?: string
}): OpenAI {
}): Promise<OpenAI> {
if (cachedClient) return cachedClient

const { default: OpenAIClass } = await import('openai')
const apiKey = process.env.GROK_API_KEY || process.env.XAI_API_KEY || ''
const baseURL = process.env.GROK_BASE_URL || DEFAULT_BASE_URL

const client = new OpenAI({
const client = new OpenAIClass({
apiKey,
baseURL,
maxRetries: options?.maxRetries ?? 0,
Expand Down
5 changes: 3 additions & 2 deletions src/services/api/grok/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function* queryModelGrok(
const openaiTools = anthropicToolsToOpenAI(standardTools)
const openaiToolChoice = anthropicToolChoiceToOpenAI(options.toolChoice)

const client = getGrokClient({
const client = await getGrokClient({
maxRetries: 0,
fetchOverride: options.fetchOverride as typeof fetch | undefined,
source: options.querySource,
Expand Down Expand Up @@ -187,7 +187,8 @@ export async function* queryModelGrok(
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logForDebugging(`[Grok] Error: ${errorMessage}`, { level: 'error' })
const stack = error instanceof Error ? `\n${error.stack}` : ''
logForDebugging(`[Grok] Error: ${errorMessage}${stack}`, { level: 'error' })
yield createAssistantAPIErrorMessage({
content: `API Error: ${errorMessage}`,
apiError: 'api_error',
Expand Down
Loading