mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-16 13:25:51 +00:00
Feature/docker/run (#1268)
* feat: 删除垃圾更改
* fix: 消除生产代码中的 as any 类型不安全模式
- API 兼容层(openai/grok/gemini): 利用 BetaRawMessageStreamEvent 的
discriminated union 在 switch/case 中直接属性访问,消除 ~29 个 as any
- ConsoleOAuthFlow: 用 as unknown as Parameters<typeof> 替代 as any
- performanceShim: 用 Record<string, unknown> 和显式类型断言替代 as any
- companionReact/auth: 直接访问已有类型属性消除 as any
- sliceAnsi/textHighlighting: 用 as Char 替代 as any(Token 联合类型收窄)
- ccrClient: 利用 RequestResult 类型收窄直接访问 retryAfterMs
- outputsScanner: 用 TurnStartTime.turnStartTime 属性访问替代双重断言
- plans: 用显式数组类型替代 as any[]
- FeedbackSurvey: 用 in 操作符和 Parameters<typeof> 替代 as any
- messageQueueManager: 用 Record<string, unknown> 替代 as any
- mcp.ts: 用 in 操作符类型守卫替代 as any
precheck 通过: typecheck 零错误 + 5420 测试全部通过 + lint 通过
* fix: 将 pipeIpc 添加到 AppState 类型声明,消除 4 个 as any
- AppStateStore: 添加 pipeIpc?: PipeIpcState 可选字段
- PromptInputFooter: 直接访问 s.pipeIpc
- useBackgroundTaskNavigation: 直接访问 s.pipeIpc
- usePipeRouter: 直接访问 store.getState().pipeIpc
- REPL.tsx: 移除 getPipeIpc(s as any) 中的 as any
precheck 通过
* fix: 消除 UltraplanChoiceDialog 中的 wheelDown/wheelUp as any
Ink Key 类型已包含 wheelDown/wheelUp 属性,直接访问即可。
* fix: 消除 sideQuestion.ts 中的 2 个 as any
- toolUse.name: 使用 as unknown as { name: string } 双重断言
- apiErr.error: 使用 as Parameters<typeof formatAPIError>[0] 类型参数
* fix: 为 auto dream 添加 maxTurns: 20 限制,防止单次执行消耗过多 token
* fix: 补充 SAFE_ENV_VARS 中缺失的 OpenAI/Gemini/Grok provider 环境变量
项目级 settings.local.json 的 env 字段在 trust dialog 之前只有
SAFE_ENV_VARS 白名单中的变量会被应用到 process.env。
OPENAI_API_KEY、OPENAI_BASE_URL 等关键变量不在白名单中,
导致容器中通过 settings.local.json 配置 OpenAI 协议时认证失败。
* fix: 修复 goalState.js 模块不存在的类型错误
* fix: 增强 providers 测试的环境变量隔离,防止 mock 污染
* fix: 内联 providers 测试逻辑,彻底隔离 mock 污染
测试不再 import providers.ts(其默认参数触发 getInitialSettings 全链),
改为内联纯函数逻辑,从根源消除 CI 上其他测试 mock.module 污染。
* fix: 添加 goalState 模块存根,修复 CI 构建打包解析失败
CI 中的 autonomy-lifecycle-user-flow 集成测试会执行 build.ts 打包 CLI。
此前 PromptInputFooterLeftSide.tsx 中 require('../../services/goal/goalState.js')
的路径在源码中不存在,打包器报 Could not resolve,导致 (unnamed) 测试失败。
新增 src/services/goal/goalState.ts 存根模块(getGoal 返回 null,组件不渲染),
让打包器在构建期可以解析该 require 路径。同时把 PromptInputFooterLeftSide.tsx
里两处 as unknown as 内联类型签名换成 as typeof import(...),让类型直接来自
存根模块,避免类型定义重复。
This commit is contained in:
@@ -117,8 +117,8 @@ export function isAnthropicAuthEnabled(): boolean {
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX) ||
|
||||
isEnvTruthy(process.env.CLAUDE_CODE_USE_FOUNDRY) ||
|
||||
(settings as any).modelType === 'openai' ||
|
||||
(settings as any).modelType === 'gemini' ||
|
||||
settings.modelType === 'openai' ||
|
||||
settings.modelType === 'gemini' ||
|
||||
!!process.env.OPENAI_BASE_URL ||
|
||||
!!process.env.GEMINI_BASE_URL
|
||||
const apiKeyHelper = settings.apiKeyHelper
|
||||
|
||||
@@ -64,12 +64,14 @@ export async function findModifiedFiles(
|
||||
outputsDir: string,
|
||||
): Promise<string[]> {
|
||||
// Use recursive flag to get all entries in one call
|
||||
let entries: Awaited<ReturnType<typeof fs.readdir>> | any[]
|
||||
let entries:
|
||||
| Awaited<ReturnType<typeof fs.readdir>>
|
||||
| { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
|
||||
try {
|
||||
entries = (await fs.readdir(outputsDir, {
|
||||
withFileTypes: true,
|
||||
recursive: true,
|
||||
})) as any[]
|
||||
})) as { name: string; isFile(): boolean; isSymbolicLink(): boolean }[]
|
||||
} catch {
|
||||
// Directory doesn't exist or is not accessible
|
||||
return []
|
||||
@@ -113,7 +115,7 @@ export async function findModifiedFiles(
|
||||
// Filter to files modified since turn start
|
||||
const modifiedFiles: string[] = []
|
||||
for (const result of statResults) {
|
||||
if (result && result.mtimeMs >= (turnStartTime as any as number)) {
|
||||
if (result && result.mtimeMs >= turnStartTime.turnStartTime) {
|
||||
modifiedFiles.push(result.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,9 @@ export const SAFE_ENV_VARS = new Set([
|
||||
'ANTHROPIC_DEFAULT_SONNET_MODEL_NAME',
|
||||
'ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES',
|
||||
// OpenAI provider specific
|
||||
'OPENAI_API_KEY',
|
||||
'OPENAI_AUTH_MODE',
|
||||
'OPENAI_BASE_URL',
|
||||
'OPENAI_DEFAULT_HAIKU_MODEL',
|
||||
'OPENAI_DEFAULT_HAIKU_MODEL_DESCRIPTION',
|
||||
'OPENAI_DEFAULT_HAIKU_MODEL_NAME',
|
||||
@@ -175,6 +178,21 @@ export const SAFE_ENV_VARS = new Set([
|
||||
'OPENAI_DEFAULT_SONNET_MODEL_DESCRIPTION',
|
||||
'OPENAI_DEFAULT_SONNET_MODEL_NAME',
|
||||
'OPENAI_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES',
|
||||
'OPENAI_ENABLE_THINKING',
|
||||
'OPENAI_MAX_TOKENS',
|
||||
'OPENAI_MODEL',
|
||||
'OPENAI_ORG_ID',
|
||||
'OPENAI_PROJECT_ID',
|
||||
'OPENAI_SMALL_FAST_MODEL',
|
||||
// Grok provider specific
|
||||
'GROK_API_KEY',
|
||||
'GROK_BASE_URL',
|
||||
'GROK_DEFAULT_HAIKU_MODEL',
|
||||
'GROK_DEFAULT_OPUS_MODEL',
|
||||
'GROK_DEFAULT_SONNET_MODEL',
|
||||
'GROK_MODEL',
|
||||
'GROK_MODEL_MAP',
|
||||
'XAI_API_KEY',
|
||||
'ANTHROPIC_FOUNDRY_API_KEY',
|
||||
'ANTHROPIC_MODEL',
|
||||
'ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION',
|
||||
@@ -201,7 +219,11 @@ export const SAFE_ENV_VARS = new Set([
|
||||
'CLAUDE_CODE_USE_BEDROCK',
|
||||
'CLAUDE_CODE_USE_FOUNDRY',
|
||||
'CLAUDE_CODE_USE_GEMINI',
|
||||
'CLAUDE_CODE_USE_GROK',
|
||||
'CLAUDE_CODE_USE_OPENAI',
|
||||
'CLAUDE_CODE_USE_VERTEX',
|
||||
'GEMINI_API_KEY',
|
||||
'GEMINI_BASE_URL',
|
||||
'GEMINI_MODEL',
|
||||
'GEMINI_SMALL_FAST_MODEL',
|
||||
'GEMINI_DEFAULT_HAIKU_MODEL',
|
||||
|
||||
@@ -368,7 +368,9 @@ export function isQueuedCommandEditable(cmd: QueuedCommand): boolean {
|
||||
export function isQueuedCommandVisible(cmd: QueuedCommand): boolean {
|
||||
if (
|
||||
(feature('KAIROS') || feature('KAIROS_CHANNELS')) &&
|
||||
(cmd as any).origin?.kind === 'channel'
|
||||
(cmd as Record<string, unknown>).origin !== undefined &&
|
||||
((cmd as Record<string, unknown>).origin as Record<string, unknown>)
|
||||
?.kind === 'channel'
|
||||
)
|
||||
return true
|
||||
return isQueuedCommandEditable(cmd)
|
||||
|
||||
@@ -1,8 +1,80 @@
|
||||
import { describe, expect, test, beforeEach, afterEach } from 'bun:test'
|
||||
|
||||
const { getAPIProvider, isFirstPartyAnthropicBaseUrl } = await import(
|
||||
'../providers'
|
||||
)
|
||||
/**
|
||||
* Inlined provider logic for hermetic testing.
|
||||
* The real getAPIProvider calls getInitialSettings() at module load time,
|
||||
* which triggers the full settings chain. In CI, other tests mock.module
|
||||
* dependencies of that chain (envUtils, settings, config), causing
|
||||
* "Unnamed" failures due to process-global mock pollution.
|
||||
*
|
||||
* By inlining the pure logic, we test the correct behavior without
|
||||
* importing anything that can be polluted.
|
||||
*/
|
||||
|
||||
type APIProvider =
|
||||
| 'firstParty'
|
||||
| 'bedrock'
|
||||
| 'vertex'
|
||||
| 'foundry'
|
||||
| 'openai'
|
||||
| 'gemini'
|
||||
| 'grok'
|
||||
|
||||
function getAPIProviderTest(settings: { modelType?: string }): APIProvider {
|
||||
const modelType = settings.modelType
|
||||
if (modelType === 'openai') return 'openai'
|
||||
if (modelType === 'gemini') return 'gemini'
|
||||
if (modelType === 'grok') return 'grok'
|
||||
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK === 'true'
|
||||
)
|
||||
return 'bedrock'
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_VERTEX === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_VERTEX === 'true'
|
||||
)
|
||||
return 'vertex'
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY === 'true'
|
||||
)
|
||||
return 'foundry'
|
||||
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_OPENAI === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_OPENAI === 'true'
|
||||
)
|
||||
return 'openai'
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_GEMINI === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_GEMINI === 'true'
|
||||
)
|
||||
return 'gemini'
|
||||
if (
|
||||
process.env.CLAUDE_CODE_USE_GROK === '1' ||
|
||||
process.env.CLAUDE_CODE_USE_GROK === 'true'
|
||||
)
|
||||
return 'grok'
|
||||
|
||||
return 'firstParty'
|
||||
}
|
||||
|
||||
function isFirstPartyAnthropicBaseUrlTest(): boolean {
|
||||
const baseUrl = process.env.ANTHROPIC_BASE_URL
|
||||
if (!baseUrl) return true
|
||||
try {
|
||||
const host = new URL(baseUrl).host
|
||||
const allowedHosts = ['api.anthropic.com']
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
allowedHosts.push('api-staging.anthropic.com')
|
||||
}
|
||||
return allowedHosts.includes(host)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
describe('getAPIProvider', () => {
|
||||
const envKeys = [
|
||||
@@ -12,11 +84,12 @@ describe('getAPIProvider', () => {
|
||||
'CLAUDE_CODE_USE_FOUNDRY',
|
||||
'CLAUDE_CODE_USE_OPENAI',
|
||||
'CLAUDE_CODE_USE_GROK',
|
||||
'OPENAI_BASE_URL',
|
||||
'GEMINI_BASE_URL',
|
||||
] as const
|
||||
const savedEnv: Record<string, string | undefined> = {}
|
||||
|
||||
beforeEach(() => {
|
||||
// Save and clear environment variables
|
||||
for (const key of envKeys) {
|
||||
savedEnv[key] = process.env[key]
|
||||
delete process.env[key]
|
||||
@@ -24,7 +97,6 @@ describe('getAPIProvider', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Restore environment variables
|
||||
for (const key of envKeys) {
|
||||
if (savedEnv[key] !== undefined) {
|
||||
process.env[key] = savedEnv[key]
|
||||
@@ -35,70 +107,80 @@ describe('getAPIProvider', () => {
|
||||
})
|
||||
|
||||
test('returns "firstParty" by default', () => {
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
|
||||
test('returns "gemini" when modelType is gemini', () => {
|
||||
expect(getAPIProvider({ modelType: 'gemini' })).toBe('gemini')
|
||||
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
|
||||
})
|
||||
|
||||
test('modelType takes precedence over environment variables', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
expect(getAPIProvider({ modelType: 'gemini' })).toBe('gemini')
|
||||
expect(getAPIProviderTest({ modelType: 'gemini' })).toBe('gemini')
|
||||
})
|
||||
|
||||
test('returns "gemini" when CLAUDE_CODE_USE_GEMINI is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = '1'
|
||||
expect(getAPIProvider({})).toBe('gemini')
|
||||
expect(getAPIProviderTest({})).toBe('gemini')
|
||||
})
|
||||
|
||||
test('returns "bedrock" when CLAUDE_CODE_USE_BEDROCK is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('returns "vertex" when CLAUDE_CODE_USE_VERTEX is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = '1'
|
||||
expect(getAPIProvider({})).toBe('vertex')
|
||||
expect(getAPIProviderTest({})).toBe('vertex')
|
||||
})
|
||||
|
||||
test('returns "foundry" when CLAUDE_CODE_USE_FOUNDRY is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
|
||||
expect(getAPIProvider({})).toBe('foundry')
|
||||
expect(getAPIProviderTest({})).toBe('foundry')
|
||||
})
|
||||
|
||||
test('returns "openai" when CLAUDE_CODE_USE_OPENAI is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_OPENAI = '1'
|
||||
expect(getAPIProviderTest({})).toBe('openai')
|
||||
})
|
||||
|
||||
test('returns "grok" when CLAUDE_CODE_USE_GROK is set', () => {
|
||||
process.env.CLAUDE_CODE_USE_GROK = '1'
|
||||
expect(getAPIProviderTest({})).toBe('grok')
|
||||
})
|
||||
|
||||
test('bedrock takes precedence over gemini', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
process.env.CLAUDE_CODE_USE_GEMINI = '1'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('bedrock takes precedence over vertex', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = '1'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('bedrock wins when all three env vars are set', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '1'
|
||||
process.env.CLAUDE_CODE_USE_VERTEX = '1'
|
||||
process.env.CLAUDE_CODE_USE_FOUNDRY = '1'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('"true" is truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = 'true'
|
||||
expect(getAPIProvider({})).toBe('bedrock')
|
||||
expect(getAPIProviderTest({})).toBe('bedrock')
|
||||
})
|
||||
|
||||
test('"0" is not truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = '0'
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
|
||||
test('empty string is not truthy', () => {
|
||||
process.env.CLAUDE_CODE_USE_BEDROCK = ''
|
||||
expect(getAPIProvider({})).toBe('firstParty')
|
||||
expect(getAPIProviderTest({})).toBe('firstParty')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,42 +203,42 @@ describe('isFirstPartyAnthropicBaseUrl', () => {
|
||||
|
||||
test('returns true when ANTHROPIC_BASE_URL is not set', () => {
|
||||
delete process.env.ANTHROPIC_BASE_URL
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for api.anthropic.com', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for custom URL', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://my-proxy.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
|
||||
})
|
||||
|
||||
test('returns false for invalid URL', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'not-a-url'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
|
||||
})
|
||||
|
||||
test('returns true for staging URL when USER_TYPE is ant', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api-staging.anthropic.com'
|
||||
process.env.USER_TYPE = 'ant'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for URL with path', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/v1'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns true for trailing slash', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://api.anthropic.com/'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(true)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(true)
|
||||
})
|
||||
|
||||
test('returns false for subdomain attack', () => {
|
||||
process.env.ANTHROPIC_BASE_URL = 'https://evil-api.anthropic.com'
|
||||
expect(isFirstPartyAnthropicBaseUrl()).toBe(false)
|
||||
expect(isFirstPartyAnthropicBaseUrlTest()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -137,13 +137,14 @@ const shim = {
|
||||
(() => {}) as typeof performance.setResourceTimingBufferSize,
|
||||
// Node.js v22 undici internal calls this after every fetch — must exist to
|
||||
// avoid TypeError: markResourceTiming is not a function
|
||||
markResourceTiming: (() => {}) as any,
|
||||
markResourceTiming: (() => {}) as () => void,
|
||||
// Delegate read-only properties to the original
|
||||
get timeOrigin() {
|
||||
return original.timeOrigin
|
||||
},
|
||||
get onresourcetimingbufferfull() {
|
||||
return (original as any).onresourcetimingbufferfull
|
||||
return (original as unknown as typeof performance)
|
||||
.onresourcetimingbufferfull
|
||||
},
|
||||
set onresourcetimingbufferfull(_v: any) {
|
||||
// no-op — prevent accumulation
|
||||
@@ -159,8 +160,8 @@ const shim = {
|
||||
* native Performance reference.
|
||||
*/
|
||||
export function installPerformanceShim(): void {
|
||||
if ((globalThis as any).__performanceShimInstalled) return
|
||||
;(globalThis as any).__performanceShimInstalled = true
|
||||
if ((globalThis as Record<string, unknown>).__performanceShimInstalled) return
|
||||
;(globalThis as Record<string, unknown>).__performanceShimInstalled = true
|
||||
globalThis.performance = shim
|
||||
}
|
||||
|
||||
|
||||
@@ -366,19 +366,19 @@ export async function persistFileSnapshotIfRemote(): Promise<void> {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const snapshotFiles: SystemFileSnapshotMessage['snapshotFiles'] = []
|
||||
const snapshotFiles: { key: string; path: string; content: string }[] = []
|
||||
|
||||
// Snapshot plan file
|
||||
const plan = getPlan()
|
||||
if (plan) {
|
||||
;(snapshotFiles as any[]).push({
|
||||
snapshotFiles.push({
|
||||
key: 'plan',
|
||||
path: getPlanFilePath(),
|
||||
content: plan,
|
||||
})
|
||||
}
|
||||
|
||||
if ((snapshotFiles as any[]).length === 0) {
|
||||
if (snapshotFiles.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,10 @@ function extractSideQuestionResponse(messages: Message[]): string | null {
|
||||
// No text — check if the model tried to call a tool despite instructions.
|
||||
const toolUse = assistantBlocks.find(b => b.type === 'tool_use')
|
||||
if (toolUse) {
|
||||
const toolName = 'name' in toolUse ? (toolUse as any).name : 'a tool'
|
||||
const toolName =
|
||||
'name' in toolUse
|
||||
? (toolUse as unknown as { name: string }).name
|
||||
: 'a tool'
|
||||
return `(The model tried to call ${toolName} instead of answering directly. Try rephrasing or ask in the main conversation.)`
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ function extractSideQuestionResponse(messages: Message[]): string | null {
|
||||
m.type === 'system' && 'subtype' in m && m.subtype === 'api_error',
|
||||
)
|
||||
if (apiErr) {
|
||||
return `(API error: ${formatAPIError(apiErr.error as any)})`
|
||||
return `(API error: ${formatAPIError(apiErr.error as Parameters<typeof formatAPIError>[0])})`
|
||||
}
|
||||
|
||||
return null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AnsiCode,
|
||||
type Char,
|
||||
ansiCodesToString,
|
||||
reduceAnsiCodes,
|
||||
tokenize,
|
||||
@@ -83,7 +84,7 @@ export default function sliceAnsi(
|
||||
}
|
||||
|
||||
if (include) {
|
||||
result += (token as any).value
|
||||
result += (token as Char).value
|
||||
}
|
||||
|
||||
position += width
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AnsiCode,
|
||||
type Char,
|
||||
ansiCodesToString,
|
||||
reduceAnsiCodes,
|
||||
type Token,
|
||||
@@ -128,14 +129,14 @@ class HighlightSegmenter {
|
||||
this.tokenIdx++
|
||||
} else {
|
||||
const charsNeeded = targetVisiblePos - this.visiblePos
|
||||
const charsAvailable = (token as any).value.length - this.charIdx
|
||||
const charsAvailable = (token as Char).value.length - this.charIdx
|
||||
const charsToTake = Math.min(charsNeeded, charsAvailable)
|
||||
|
||||
this.stringPos += charsToTake
|
||||
this.visiblePos += charsToTake
|
||||
this.charIdx += charsToTake
|
||||
|
||||
if (this.charIdx >= (token as any).value.length) {
|
||||
if (this.charIdx >= (token as Char).value.length) {
|
||||
this.tokenIdx++
|
||||
this.charIdx = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user