Compare commits

...

4 Commits

Author SHA1 Message Date
claude-code-best
6536757428 feat: 对其他 provider 提供 langfuse 监控 2026-04-19 09:09:27 +08:00
claude-code-best
a0dc4540ca fix: 修复服务器两个 / 的问题 2026-04-19 08:55:55 +08:00
claude-code-best
7e4df5c3e9 build: 更改构建逻辑 2026-04-19 08:45:06 +08:00
claude-code-best
4d939e5722 chore: 更新构建 feature 的问题 2026-04-19 08:27:25 +08:00
10 changed files with 1127 additions and 236 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
registry=https://registry.npmjs.org/

View File

@@ -11,6 +11,7 @@ rmSync(outdir, { recursive: true, force: true })
// Default features that match the official CLI build.
// Additional features can be enabled via FEATURE_<NAME>=1 env vars.
const DEFAULT_BUILD_FEATURES = [
'BUDDY', 'TRANSCRIPT_CLASSIFIER', 'BRIDGE_MODE',
'AGENT_TRIGGERS_REMOTE',
'CHICAGO_MCP',
'VOICE_MODE',

1220
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "claude-code-best",
"version": "1.4.2",
"version": "1.4.4",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",
@@ -37,6 +37,7 @@
"files": [
"dist",
"scripts/postinstall.cjs",
"scripts/run-parallel.mjs",
"scripts/setup-chrome-mcp.mjs"
],
"scripts": {
@@ -72,20 +73,20 @@
"@ant/computer-use-mcp": "workspace:*",
"@ant/computer-use-swift": "workspace:*",
"@anthropic-ai/bedrock-sdk": "^0.26.4",
"@anthropic-ai/claude-agent-sdk": "^0.2.87",
"@anthropic-ai/claude-agent-sdk": "^0.2.114",
"@anthropic-ai/foundry-sdk": "^0.2.3",
"@anthropic-ai/mcpb": "^2.1.2",
"@anthropic-ai/sandbox-runtime": "^0.0.44",
"@anthropic-ai/sdk": "^0.80.0",
"@anthropic-ai/vertex-sdk": "^0.14.4",
"@anthropic/ink": "workspace:*",
"@aws-sdk/client-bedrock": "^3.1020.0",
"@aws-sdk/client-bedrock-runtime": "^3.1020.0",
"@aws-sdk/client-sts": "^3.1020.0",
"@aws-sdk/credential-provider-node": "^3.972.28",
"@aws-sdk/credential-providers": "^3.1020.0",
"@aws-sdk/client-bedrock": "^3.1032.0",
"@aws-sdk/client-bedrock-runtime": "^3.1032.0",
"@aws-sdk/client-sts": "^3.1032.0",
"@aws-sdk/credential-provider-node": "^3.972.32",
"@aws-sdk/credential-providers": "^3.1032.0",
"@azure/identity": "^4.13.1",
"@biomejs/biome": "^2.4.10",
"@biomejs/biome": "^2.4.12",
"@claude-code-best/agent-tools": "workspace:*",
"@claude-code-best/builtin-tools": "workspace:*",
"@claude-code-best/mcp-client": "workspace:*",
@@ -96,7 +97,7 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.214.0",
"@opentelemetry/core": "^2.6.1",
"@opentelemetry/core": "^2.7.0",
"@opentelemetry/exporter-logs-otlp-grpc": "^0.214.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
"@opentelemetry/exporter-logs-otlp-proto": "^0.214.0",
@@ -107,14 +108,14 @@
"@opentelemetry/exporter-trace-otlp-grpc": "^0.214.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.214.0",
"@opentelemetry/resources": "^2.6.1",
"@opentelemetry/resources": "^2.7.0",
"@opentelemetry/sdk-logs": "^0.214.0",
"@opentelemetry/sdk-metrics": "^2.6.1",
"@opentelemetry/sdk-trace-base": "^2.6.1",
"@opentelemetry/sdk-metrics": "^2.7.0",
"@opentelemetry/sdk-trace-base": "^2.7.0",
"@opentelemetry/semantic-conventions": "^1.40.0",
"@sentry/node": "^10.47.0",
"@smithy/core": "^3.23.13",
"@smithy/node-http-handler": "^4.5.1",
"@sentry/node": "^10.49.0",
"@smithy/core": "^3.23.15",
"@smithy/node-http-handler": "^4.5.3",
"@types/bun": "^1.3.12",
"@types/cacache": "^20.0.1",
"@types/he": "^1.2.3",
@@ -136,7 +137,7 @@
"asciichart": "^1.5.25",
"audio-capture-napi": "workspace:*",
"auto-bind": "^5.0.1",
"axios": "^1.14.0",
"axios": "^1.15.0",
"bidi-js": "^1.0.3",
"cacache": "^20.0.4",
"chalk": "^5.6.2",
@@ -151,7 +152,7 @@
"execa": "^9.6.1",
"fflate": "^0.8.2",
"figures": "^6.1.0",
"fuse.js": "^7.1.0",
"fuse.js": "^7.3.0",
"get-east-asian-width": "^1.5.0",
"google-auth-library": "^10.6.2",
"he": "^1.2.0",
@@ -161,21 +162,21 @@
"image-processor-napi": "workspace:*",
"indent-string": "^5.0.0",
"jsonc-parser": "^3.3.1",
"knip": "^6.1.1",
"lodash-es": "^4.17.23",
"lru-cache": "^11.2.7",
"marked": "^17.0.5",
"knip": "^6.4.1",
"lodash-es": "^4.18.1",
"lru-cache": "^11.3.5",
"marked": "^17.0.6",
"modifiers-napi": "workspace:*",
"openai": "^6.33.0",
"openai": "^6.34.0",
"p-map": "^7.0.4",
"picomatch": "^4.0.4",
"plist": "^3.1.0",
"proper-lockfile": "^4.1.2",
"qrcode": "^1.5.4",
"react": "^19.2.4",
"react": "^19.2.5",
"react-compiler-runtime": "^1.0.0",
"react-reconciler": "^0.33.0",
"rollup": "^4.60.1",
"rollup": "^4.60.2",
"semver": "^7.7.4",
"sharp": "^0.34.5",
"shell-quote": "^1.8.3",
@@ -184,10 +185,10 @@
"strip-ansi": "^7.2.0",
"supports-hyperlinks": "^4.4.0",
"tree-kill": "^1.2.2",
"turndown": "^7.2.2",
"type-fest": "^5.5.0",
"typescript": "^6.0.2",
"undici": "^7.24.6",
"turndown": "^7.2.4",
"type-fest": "^5.6.0",
"typescript": "^6.0.3",
"undici": "^7.25.0",
"url-handler-napi": "workspace:*",
"usehooks-ts": "^3.1.1",
"vite": "^8.0.8",

View File

@@ -18,6 +18,6 @@ export const config = {
} as const;
export function getBaseUrl(): string {
if (config.baseUrl) return config.baseUrl;
return `http://localhost:${config.port}`;
const url = config.baseUrl || `http://localhost:${config.port}`;
return url.replace(/\/+$/, "");
}

View File

@@ -33,6 +33,17 @@ const app = new Hono();
// Middleware
app.use("*", logger());
app.use("*", async (c, next) => {
// Normalize double slashes in path (e.g. //v1/environments/bridge → /v1/environments/bridge)
const path = new URL(c.req.url).pathname;
if (path.includes("//")) {
const normalized = path.replace(/\/+/g, "/");
const url = new URL(c.req.url);
url.pathname = normalized;
return app.fetch(new Request(url.toString(), c.req.raw));
}
await next();
});
app.use("/web/*", cors());
// Health check

View File

@@ -1 +1,2 @@
ACP_RCS_URL=http://localhost:3000 ACP_RCS_TOKEN=test-my-key acp-link ccb-bun -- --acp
# ACP_RCS_URL=http://localhost:3000 ACP_RCS_TOKEN=test-my-key acp-link ccb-bun -- --acp
ACP_RCS_URL=https://remote-control.claude-code-best.win/ ACP_RCS_TOKEN=test-my-key acp-link ccb-bun -- --acp

View File

@@ -18,6 +18,8 @@ import type { SDKAssistantMessageError } from '../../../entrypoints/agentSdkType
import type { SystemPrompt } from '../../../utils/systemPromptType.js'
import type { ThinkingConfig } from '../../../utils/thinking.js'
import type { Options } from '../claude.js'
import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
import { convertMessagesToLangfuse, convertOutputToLangfuse, convertToolsToLangfuse } from '../../../services/langfuse/convert.js'
import { streamGeminiGenerateContent } from './client.js'
import { anthropicMessagesToGemini, resolveGeminiModel, adaptGeminiStreamToAnthropic, anthropicToolsToGemini, anthropicToolChoiceToGemini, GEMINI_THOUGHT_SIGNATURE_FIELD } from '@ant/model-provider'
@@ -100,6 +102,7 @@ export async function* queryModelGemini(
const adaptedStream = adaptGeminiStreamToAnthropic(stream, geminiModel)
const contentBlocks: Record<number, any> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any = undefined
let ttftMs = 0
const start = Date.now()
@@ -160,6 +163,7 @@ export async function* queryModelGemini(
uuid: randomUUID(),
timestamp: new Date().toISOString(),
}
collectedMessages.push(message)
yield message
break
}
@@ -174,6 +178,22 @@ export async function* queryModelGemini(
...(event.type === 'message_start' ? { ttftMs } : undefined),
} as StreamEvent
}
// Record LLM observation in Langfuse (no-op if not configured)
recordLLMObservation(options.langfuseTrace ?? null, {
model: geminiModel,
provider: 'gemini',
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
output: convertOutputToLangfuse(collectedMessages),
usage: {
input_tokens: 0,
output_tokens: 0,
},
startTime: new Date(start),
endTime: new Date(),
completionStartTime: ttftMs > 0 ? new Date(start + ttftMs) : undefined,
tools: convertToolsToLangfuse(toolSchemas as unknown[]),
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logForDebugging(`[Gemini] Error: ${errorMessage}`, { level: 'error' })

View File

@@ -14,6 +14,8 @@ import { toolToAPISchema } from '../../../utils/api.js'
import { logForDebugging } from '../../../utils/debug.js'
import { addToTotalSessionCost } from '../../../cost-tracker.js'
import { calculateUSDCost } from '../../../utils/modelCost.js'
import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
import { convertMessagesToLangfuse, convertOutputToLangfuse, convertToolsToLangfuse } from '../../../services/langfuse/convert.js'
import type { Options } from '../claude.js'
import { randomUUID } from 'crypto'
import {
@@ -92,6 +94,7 @@ export async function* queryModelGrok(
const adaptedStream = adaptOpenAIStreamToAnthropic(stream as AsyncIterable<ChatCompletionChunk>, grokModel)
const contentBlocks: Record<number, any> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any = undefined
let usage = {
input_tokens: 0,
@@ -157,6 +160,7 @@ export async function* queryModelGrok(
uuid: randomUUID(),
timestamp: new Date().toISOString(),
}
collectedMessages.push(m)
yield m
break
}
@@ -182,6 +186,24 @@ export async function* queryModelGrok(
...(event.type === 'message_start' ? { ttftMs } : undefined),
} as StreamEvent
}
// Record LLM observation in Langfuse (no-op if not configured)
recordLLMObservation(options.langfuseTrace ?? null, {
model: grokModel,
provider: 'grok',
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
output: convertOutputToLangfuse(collectedMessages),
usage: {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
cache_creation_input_tokens: usage.cache_creation_input_tokens,
cache_read_input_tokens: usage.cache_read_input_tokens,
},
startTime: new Date(start),
endTime: new Date(),
completionStartTime: ttftMs > 0 ? new Date(start + ttftMs) : undefined,
tools: convertToolsToLangfuse(toolSchemas as unknown[]),
})
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
logForDebugging(`[Grok] Error: ${errorMessage}`, { level: 'error' })

View File

@@ -24,6 +24,8 @@ import { logForDebugging } from '../../../utils/debug.js'
import { addToTotalSessionCost } from '../../../cost-tracker.js'
import { calculateUSDCost } from '../../../utils/modelCost.js'
import { isOpenAIThinkingEnabled, resolveOpenAIMaxTokens, buildOpenAIRequestBody } from './requestBody.js'
import { recordLLMObservation } from '../../../services/langfuse/tracing.js'
import { convertMessagesToLangfuse, convertOutputToLangfuse, convertToolsToLangfuse } from '../../../services/langfuse/convert.js'
export { isOpenAIThinkingEnabled, resolveOpenAIMaxTokens, buildOpenAIRequestBody }
import { getModelMaxOutputTokens } from '../../../utils/context.js'
import type { Options } from '../claude.js'
@@ -246,6 +248,7 @@ export async function* queryModelOpenAI(
// Accumulate content blocks and usage, same as the Anthropic path in claude.ts
const contentBlocks: Record<number, any> = {}
const collectedMessages: AssistantMessage[] = []
let partialMessage: any
let stopReason: string | null = null
let usage = {
@@ -323,6 +326,9 @@ export async function* queryModelOpenAI(
partialMessage, contentBlocks, tools, agentId: options.agentId,
usage, stopReason, maxTokens,
})) {
if (output.type === 'assistant') {
collectedMessages.push(output)
}
yield output
}
// Reset partialMessage so the post-loop safety fallback does not
@@ -346,6 +352,24 @@ export async function* queryModelOpenAI(
} as StreamEvent
}
// Record LLM observation in Langfuse (no-op if not configured)
recordLLMObservation(options.langfuseTrace ?? null, {
model: openaiModel,
provider: 'openai',
input: convertMessagesToLangfuse(messagesForAPI, systemPrompt),
output: convertOutputToLangfuse(collectedMessages),
usage: {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
cache_creation_input_tokens: usage.cache_creation_input_tokens,
cache_read_input_tokens: usage.cache_read_input_tokens,
},
startTime: new Date(start),
endTime: new Date(),
completionStartTime: ttftMs > 0 ? new Date(start + ttftMs) : undefined,
tools: convertToolsToLangfuse(toolSchemas as unknown[]),
})
// Safety: if stream ended without message_stop, assemble and yield whatever we have
if (partialMessage) {
for (const output of assembleFinalAssistantOutputs({