upgrade for win & remove some un-use code
This commit is contained in:
parent
6bbaa6c559
commit
d0d1dca009
12
README.md
12
README.md
@ -96,6 +96,18 @@ After installation, you can use any of these commands:
|
||||
- `kwa` - Kode With Agent (alternative)
|
||||
- `kd` - Ultra-short alias
|
||||
|
||||
### Windows Notes
|
||||
|
||||
- Install Git for Windows to provide a Bash (Unix‑like) environment: https://git-scm.com/download/win
|
||||
- Kode automatically prefers Git Bash/MSYS or WSL Bash when available.
|
||||
- If neither is available, it will fall back to your default shell, but many features work best with Bash.
|
||||
- Use VS Code’s integrated terminal rather than legacy Command Prompt (cmd):
|
||||
- Better font rendering and icon support.
|
||||
- Fewer path and encoding quirks compared to cmd.
|
||||
- Select “Git Bash” as the VS Code terminal shell when possible.
|
||||
- Optional: If you install globally via npm, avoid spaces in the global prefix path to prevent shim issues.
|
||||
- Example: `npm config set prefix "C:\\npm"` and reinstall global packages.
|
||||
|
||||
## Usage
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
@ -50,6 +50,17 @@ npm install -g @shareai-lab/kode
|
||||
- `kwa` - Kode With Agent(备选)
|
||||
- `kd` - 超短别名
|
||||
|
||||
### Windows 提示
|
||||
|
||||
- 请安装 Git for Windows(包含 Git Bash 类 Unix 终端):https://git-scm.com/download/win
|
||||
- Kode 会优先使用 Git Bash/MSYS 或 WSL Bash;没有时会回退到默认终端,但在 Bash 下体验更佳。
|
||||
- 推荐在 VS Code 的集成终端中运行(而非系统默认的 cmd):
|
||||
- 字体与图标显示更稳定,UI 体验更好。
|
||||
- 相比 cmd 路径/编码等兼容性问题更少。
|
||||
- 在 VS Code 终端中选择 “Git Bash” 作为默认 Shell。
|
||||
- 可选:若通过 npm 全局安装,建议避免将 npm 全局 prefix 设置在含空格的路径,以免生成的可执行 shim 出现路径解析问题。
|
||||
- 示例:`npm config set prefix "C:\\npm"`,然后重新安装全局包。
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 交互模式
|
||||
|
||||
@ -51,8 +51,6 @@
|
||||
"@commander-js/extra-typings": "^13.1.0",
|
||||
"@inkjs/ui": "^2.0.0",
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"@statsig/client-core": "^3.18.2",
|
||||
"@statsig/js-client": "^3.18.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^19.1.8",
|
||||
"ansi-escapes": "^7.0.0",
|
||||
|
||||
@ -11,7 +11,6 @@ import { getGitState, getIsGit, GitRepoState } from '../utils/git'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize'
|
||||
import { getAnthropicApiKey, getGlobalConfig } from '../utils/config'
|
||||
import { USER_AGENT } from '../utils/http'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { PRODUCT_NAME } from '../constants/product'
|
||||
import { API_ERROR_MESSAGE_PREFIX, queryQuick } from '../services/claude'
|
||||
import { openBrowser } from '../utils/browser'
|
||||
|
||||
@ -3,7 +3,6 @@ import { Static, Box, Text, useInput } from 'ink'
|
||||
import TextInput from './TextInput'
|
||||
import { OAuthService, createAndStoreApiKey } from '../services/oauth'
|
||||
import { getTheme } from '../utils/theme'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { AsciiLogo } from './AsciiLogo'
|
||||
import { useTerminalSize } from '../hooks/useTerminalSize'
|
||||
import { logError } from '../utils/log'
|
||||
@ -70,10 +69,10 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
|
||||
useInput(async (_, key) => {
|
||||
if (key.return) {
|
||||
if (oauthStatus.state === 'idle') {
|
||||
logEvent('tengu_oauth_start', {})
|
||||
|
||||
setOAuthStatus({ state: 'ready_to_start' })
|
||||
} else if (oauthStatus.state === 'success') {
|
||||
logEvent('tengu_oauth_success', {})
|
||||
|
||||
await clearTerminal() // needed to clear out Static components
|
||||
onDone()
|
||||
} else if (oauthStatus.state === 'error' && oauthStatus.toRetry) {
|
||||
@ -101,7 +100,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
|
||||
}
|
||||
|
||||
// Track which path the user is taking (manual code entry)
|
||||
logEvent('tengu_oauth_manual_entry', {})
|
||||
|
||||
oauthService.processCallback({
|
||||
authorizationCode,
|
||||
state,
|
||||
@ -133,7 +132,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
|
||||
'Failed to exchange authorization code for access token. Please try again.',
|
||||
toRetry: { state: 'ready_to_start' },
|
||||
})
|
||||
logEvent('tengu_oauth_token_exchange_error', { error: err.message })
|
||||
|
||||
} else {
|
||||
// Handle other errors
|
||||
setOAuthStatus({
|
||||
@ -154,7 +153,7 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
|
||||
message: 'Failed to create API key: ' + err.message,
|
||||
toRetry: { state: 'ready_to_start' },
|
||||
})
|
||||
logEvent('tengu_oauth_api_key_error', { error: err.message })
|
||||
|
||||
throw err
|
||||
},
|
||||
)
|
||||
@ -169,13 +168,10 @@ export function ConsoleOAuthFlow({ onDone }: Props): React.ReactNode {
|
||||
"Unable to create API key. The server accepted the request but didn't return a key.",
|
||||
toRetry: { state: 'ready_to_start' },
|
||||
})
|
||||
logEvent('tengu_oauth_api_key_error', {
|
||||
error: 'server_returned_no_key',
|
||||
})
|
||||
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = (err as Error).message
|
||||
logEvent('tengu_oauth_error', { error: errorMessage })
|
||||
}
|
||||
}, [oauthService, setShowPastePrompt])
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
isNotEmptyMessage,
|
||||
normalizeMessages,
|
||||
} from '../utils/messages.js'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import type { AssistantMessage, UserMessage } from '../query'
|
||||
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
|
||||
|
||||
@ -37,23 +36,14 @@ export function MessageSelector({
|
||||
}: Props): React.ReactNode {
|
||||
const currentUUID = useMemo(randomUUID, [])
|
||||
|
||||
// Log when selector is opened
|
||||
useEffect(() => {
|
||||
logEvent('tengu_message_selector_opened', {})
|
||||
}, [])
|
||||
useEffect(() => {}, [])
|
||||
|
||||
function handleSelect(message: UserMessage) {
|
||||
const indexFromEnd = messages.length - 1 - messages.indexOf(message)
|
||||
logEvent('tengu_message_selector_selected', {
|
||||
index_from_end: indexFromEnd.toString(),
|
||||
message_type: message.type,
|
||||
is_current_prompt: (message.uuid === currentUUID).toString(),
|
||||
})
|
||||
onSelect(message)
|
||||
}
|
||||
|
||||
function handleEscape() {
|
||||
logEvent('tengu_message_selector_cancelled', {})
|
||||
onEscape()
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
getCurrentProjectConfig,
|
||||
} from '../utils/config.js'
|
||||
import { PRODUCT_NAME } from '../constants/product'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { useExitOnCtrlCD } from '../hooks/useExitOnCtrlCD'
|
||||
import { homedir } from 'os'
|
||||
import { getCwd } from '../utils/state'
|
||||
@ -19,20 +18,13 @@ type Props = {
|
||||
|
||||
export function TrustDialog({ onDone }: Props): React.ReactNode {
|
||||
const theme = getTheme()
|
||||
React.useEffect(() => {
|
||||
// Log when dialog is shown
|
||||
logEvent('trust_dialog_shown', {})
|
||||
}, [])
|
||||
React.useEffect(() => {}, [])
|
||||
|
||||
function onChange(value: 'yes' | 'no') {
|
||||
const config = getCurrentProjectConfig()
|
||||
switch (value) {
|
||||
case 'yes': {
|
||||
// Log when user accepts
|
||||
const isHomeDir = homedir() === getCwd()
|
||||
logEvent('trust_dialog_accept', {
|
||||
isHomeDir: String(isHomeDir),
|
||||
})
|
||||
|
||||
if (!isHomeDir) {
|
||||
saveCurrentProjectConfig({
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { TextBlock, ToolUseBlock } from '@anthropic-ai/sdk/resources/index.mjs'
|
||||
import { AssistantMessage, BinaryFeedbackResult } from '../../query'
|
||||
import { MAIN_QUERY_TEMPERATURE } from '../../services/claude'
|
||||
import { getDynamicConfig, logEvent } from '../../services/statsig'
|
||||
|
||||
import { isEqual, zip } from 'lodash-es'
|
||||
import { getGitState } from '../../utils/git'
|
||||
@ -19,9 +18,8 @@ type BinaryFeedbackConfig = {
|
||||
}
|
||||
|
||||
async function getBinaryFeedbackStatsigConfig(): Promise<BinaryFeedbackConfig> {
|
||||
return await getDynamicConfig('tengu-binary-feedback-config', {
|
||||
sampleFrequency: 0,
|
||||
})
|
||||
|
||||
return { sampleFrequency: 0 }
|
||||
}
|
||||
|
||||
function getMessageBlockSequence(m: AssistantMessage) {
|
||||
@ -40,38 +38,14 @@ export async function logBinaryFeedbackEvent(
|
||||
const modelA = m1.message.model
|
||||
const modelB = m2.message.model
|
||||
const gitState = await getGitState()
|
||||
logEvent('tengu_binary_feedback', {
|
||||
msg_id_A: m1.message.id,
|
||||
msg_id_B: m2.message.id,
|
||||
choice: {
|
||||
'prefer-left': m1.message.id,
|
||||
'prefer-right': m2.message.id,
|
||||
neither: undefined,
|
||||
'no-preference': undefined,
|
||||
}[choice],
|
||||
choiceStr: choice,
|
||||
gitHead: gitState?.commitHash,
|
||||
gitBranch: gitState?.branchName,
|
||||
gitRepoRemoteUrl: gitState?.remoteUrl || undefined,
|
||||
gitRepoIsHeadOnRemote: gitState?.isHeadOnRemote?.toString(),
|
||||
gitRepoIsClean: gitState?.isClean?.toString(),
|
||||
modelA,
|
||||
modelB,
|
||||
temperatureA: String(MAIN_QUERY_TEMPERATURE),
|
||||
temperatureB: String(MAIN_QUERY_TEMPERATURE),
|
||||
seqA: String(getMessageBlockSequence(m1)),
|
||||
seqB: String(getMessageBlockSequence(m2)),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export async function logBinaryFeedbackSamplingDecision(
|
||||
decision: boolean,
|
||||
reason?: string,
|
||||
): Promise<void> {
|
||||
logEvent('tengu_binary_feedback_sampling_decision', {
|
||||
decision: decision.toString(),
|
||||
reason,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export async function logBinaryFeedbackDisplayDecision(
|
||||
@ -80,14 +54,7 @@ export async function logBinaryFeedbackDisplayDecision(
|
||||
m2: AssistantMessage,
|
||||
reason?: string,
|
||||
): Promise<void> {
|
||||
logEvent('tengu_binary_feedback_display_decision', {
|
||||
decision: decision.toString(),
|
||||
reason,
|
||||
msg_id_A: m1.message.id,
|
||||
msg_id_B: m2.message.id,
|
||||
seqA: String(getMessageBlockSequence(m1)),
|
||||
seqB: String(getMessageBlockSequence(m2)),
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function textContentBlocksEqual(cb1: TextBlock, cb2: TextBlock): boolean {
|
||||
|
||||
@ -4,7 +4,6 @@ import { useMemo } from 'react'
|
||||
import { Tool } from '../../../Tool'
|
||||
import { GlobTool } from '../../../tools/GlobTool/GlobTool'
|
||||
import { GrepTool } from '../../../tools/GrepTool/GrepTool'
|
||||
import { logEvent } from '../../../services/statsig'
|
||||
|
||||
function getToolUseFromMessages(
|
||||
toolUseID: string,
|
||||
@ -46,7 +45,7 @@ export function useGetToolFromMessages(
|
||||
_ => _.name === toolUse.name,
|
||||
)
|
||||
if (tool === GlobTool || tool === GrepTool) {
|
||||
logEvent('tengu_legacy_tool_lookup', {})
|
||||
|
||||
}
|
||||
if (!tool) {
|
||||
throw new ReferenceError(`Tool not found for ${toolUse.name}`)
|
||||
|
||||
@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
||||
import { logUnaryEvent, CompletionType } from '../../utils/unaryLogging'
|
||||
import { ToolUseConfirm } from '../../components/permissions/PermissionRequest'
|
||||
import { env } from '../../utils/env'
|
||||
import { logEvent } from '../../services/statsig'
|
||||
|
||||
|
||||
type UnaryEventType = {
|
||||
completion_type: CompletionType
|
||||
@ -19,11 +19,7 @@ export function usePermissionRequestLogging(
|
||||
unaryEvent: UnaryEventType,
|
||||
): void {
|
||||
useEffect(() => {
|
||||
// Log Statsig event
|
||||
logEvent('tengu_tool_use_show_permission_request', {
|
||||
messageID: toolUseConfirm.assistantMessage.message.id,
|
||||
toolName: toolUseConfirm.tool.name,
|
||||
})
|
||||
|
||||
|
||||
// Handle string or Promise language name
|
||||
const languagePromise = Promise.resolve(unaryEvent.language_name)
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export const GATE_TOKEN_EFFICIENT_TOOLS = 'tengu-token-efficient-tools'
|
||||
export const BETA_HEADER_TOKEN_EFFICIENT_TOOLS =
|
||||
'token-efficient-tools-2024-12-11'
|
||||
export const GATE_USE_EXTERNAL_UPDATER = 'tengu-use-external-updater'
|
||||
export const CLAUDE_CODE_20250219_BETA_HEADER = 'claude-code-20250219'
|
||||
@ -1,3 +0,0 @@
|
||||
export const SENTRY_DSN = ''
|
||||
|
||||
export const STATSIG_CLIENT_KEY = ''
|
||||
@ -89,7 +89,7 @@ import {
|
||||
ensureConfigScope,
|
||||
} from '../services/mcpClient'
|
||||
import { handleMcprcServerApprovals } from '../services/mcpServerApproval'
|
||||
import { checkGate, initializeStatsig, logEvent } from '../services/statsig'
|
||||
|
||||
import { getExampleCommands } from '../utils/exampleCommands'
|
||||
import { cursorShow } from 'ansi-escapes'
|
||||
import { getLatestVersion, assertMinVersion, getUpdateCommandSuggestions } from '../utils/autoUpdater'
|
||||
@ -97,7 +97,7 @@ import { gt } from 'semver'
|
||||
import { CACHE_PATHS } from '../utils/log'
|
||||
// import { checkAndNotifyUpdate } from '../utils/autoUpdater'
|
||||
import { PersistentShell } from '../utils/PersistentShell'
|
||||
import { GATE_USE_EXTERNAL_UPDATER } from '../constants/betas'
|
||||
// Vendor beta gates removed
|
||||
import { clearTerminal } from '../utils/terminal'
|
||||
import { showInvalidConfigDialog } from '../components/InvalidConfigDialog'
|
||||
import { ConfigParseError } from '../utils/errors'
|
||||
@ -269,12 +269,7 @@ async function setup(cwd: string, safeMode?: boolean): Promise<void> {
|
||||
projectConfig.lastCost !== undefined &&
|
||||
projectConfig.lastDuration !== undefined
|
||||
) {
|
||||
logEvent('tengu_exit', {
|
||||
last_session_cost: String(projectConfig.lastCost),
|
||||
last_session_api_duration: String(projectConfig.lastAPIDuration),
|
||||
last_session_duration: String(projectConfig.lastDuration),
|
||||
last_session_id: projectConfig.lastSessionId,
|
||||
})
|
||||
|
||||
// Clear the values after logging
|
||||
// saveCurrentProjectConfig({
|
||||
// ...projectConfig,
|
||||
@ -321,9 +316,7 @@ async function main() {
|
||||
let renderContext: RenderOptions | undefined = {
|
||||
exitOnCtrlC: false,
|
||||
|
||||
onFlicker() {
|
||||
logEvent('tengu_flicker', {})
|
||||
},
|
||||
onFlicker() {},
|
||||
} as any
|
||||
|
||||
if (
|
||||
@ -400,15 +393,7 @@ ${commandList}`,
|
||||
.action(
|
||||
async (prompt, { cwd, debug, verbose, enableArchitect, print, safe }) => {
|
||||
await showSetupScreens(safe, print)
|
||||
logEvent('tengu_init', {
|
||||
entrypoint: PRODUCT_COMMAND,
|
||||
hasInitialPrompt: Boolean(prompt).toString(),
|
||||
hasStdin: Boolean(stdinContent).toString(),
|
||||
enableArchitect: enableArchitect?.toString() ?? 'false',
|
||||
verbose: verbose?.toString() ?? 'false',
|
||||
debug: debug?.toString() ?? 'false',
|
||||
print: print?.toString() ?? 'false',
|
||||
})
|
||||
|
||||
await setup(cwd, safe)
|
||||
|
||||
assertMinVersion()
|
||||
@ -574,10 +559,6 @@ ${commandList}`,
|
||||
.description('Remove a tool from the list of approved tools')
|
||||
.action(async (tool: string) => {
|
||||
const result = handleRemoveApprovedTool(tool)
|
||||
logEvent('tengu_approved_tool_remove', {
|
||||
tool,
|
||||
success: String(result.success),
|
||||
})
|
||||
console.log(result.message)
|
||||
process.exit(result.success ? 0 : 1)
|
||||
})
|
||||
@ -593,7 +574,6 @@ ${commandList}`,
|
||||
.description(`Start the ${PRODUCT_NAME} MCP server`)
|
||||
.action(async () => {
|
||||
const providedCwd = (program.opts() as { cwd?: string }).cwd ?? cwd()
|
||||
logEvent('tengu_mcp_start', { providedCwd })
|
||||
|
||||
// Verify the directory exists
|
||||
if (!existsSync(providedCwd)) {
|
||||
@ -621,7 +601,6 @@ ${commandList}`,
|
||||
.action(async (name, url, options) => {
|
||||
try {
|
||||
const scope = ensureConfigScope(options.scope)
|
||||
logEvent('tengu_mcp_add', { name, type: 'sse', scope })
|
||||
|
||||
addMcpServer(name, { type: 'sse', url }, scope)
|
||||
console.log(
|
||||
@ -717,11 +696,7 @@ ${commandList}`,
|
||||
|
||||
// Add the server
|
||||
if (type === 'sse') {
|
||||
logEvent('tengu_mcp_add', {
|
||||
name: serverName,
|
||||
type: 'sse',
|
||||
scope: serverScope,
|
||||
})
|
||||
|
||||
addMcpServer(
|
||||
serverName,
|
||||
{ type: 'sse', url: commandOrUrlValue },
|
||||
@ -731,11 +706,7 @@ ${commandList}`,
|
||||
`Added SSE MCP server ${serverName} with URL ${commandOrUrlValue} to ${serverScope} config`,
|
||||
)
|
||||
} else {
|
||||
logEvent('tengu_mcp_add', {
|
||||
name: serverName,
|
||||
type: 'stdio',
|
||||
scope: serverScope,
|
||||
})
|
||||
|
||||
addMcpServer(
|
||||
serverName,
|
||||
{
|
||||
@ -757,13 +728,13 @@ ${commandList}`,
|
||||
|
||||
// Check if it's an SSE URL (starts with http:// or https://)
|
||||
if (commandOrUrl.match(/^https?:\/\//)) {
|
||||
logEvent('tengu_mcp_add', { name, type: 'sse', scope })
|
||||
|
||||
addMcpServer(name, { type: 'sse', url: commandOrUrl }, scope)
|
||||
console.log(
|
||||
`Added SSE MCP server ${name} with URL ${commandOrUrl} to ${scope} config`,
|
||||
)
|
||||
} else {
|
||||
logEvent('tengu_mcp_add', { name, type: 'stdio', scope })
|
||||
|
||||
const env = parseEnvVars(options.env)
|
||||
addMcpServer(
|
||||
name,
|
||||
@ -799,7 +770,7 @@ ${commandList}`,
|
||||
.action(async (name: string, options: { scope?: string }) => {
|
||||
try {
|
||||
const scope = ensureConfigScope(options.scope)
|
||||
logEvent('tengu_mcp_delete', { name, scope })
|
||||
|
||||
|
||||
removeMcpServer(name, scope)
|
||||
console.log(`Removed MCP server ${name} from ${scope} config`)
|
||||
@ -814,7 +785,6 @@ ${commandList}`,
|
||||
.command('list')
|
||||
.description('List configured MCP servers')
|
||||
.action(() => {
|
||||
logEvent('tengu_mcp_list', {})
|
||||
const servers = listMCPServers()
|
||||
if (Object.keys(servers).length === 0) {
|
||||
console.log(
|
||||
@ -873,7 +843,7 @@ ${commandList}`,
|
||||
}
|
||||
|
||||
// Add server with the provided config
|
||||
logEvent('tengu_mcp_add_json', { name, type: serverConfig.type, scope })
|
||||
|
||||
addMcpServer(name, serverConfig, scope)
|
||||
|
||||
if (serverConfig.type === 'sse') {
|
||||
@ -899,7 +869,7 @@ ${commandList}`,
|
||||
.command('get <name>')
|
||||
.description('Get details about an MCP server')
|
||||
.action((name: string) => {
|
||||
logEvent('tengu_mcp_get', { name })
|
||||
|
||||
const server = getMcpServer(name)
|
||||
if (!server) {
|
||||
console.error(`No MCP server found with name: ${name}`)
|
||||
@ -1227,7 +1197,7 @@ ${commandList}`,
|
||||
'Reset all approved and rejected project-scoped (.mcp.json) servers within this project',
|
||||
)
|
||||
.action(() => {
|
||||
logEvent('tengu_mcp_reset_project_choices', {})
|
||||
|
||||
resetMcpChoices()
|
||||
})
|
||||
|
||||
@ -1239,7 +1209,7 @@ ${commandList}`,
|
||||
'Reset all approved and rejected .mcprc servers for this project',
|
||||
)
|
||||
.action(() => {
|
||||
logEvent('tengu_mcp_reset_mcprc_choices', {})
|
||||
|
||||
resetMcpChoices()
|
||||
})
|
||||
}
|
||||
@ -1249,7 +1219,7 @@ ${commandList}`,
|
||||
.command('doctor')
|
||||
.description(`Check the health of your ${PRODUCT_NAME} installation`)
|
||||
.action(async () => {
|
||||
logEvent('tengu_doctor_command', {})
|
||||
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
;(async () => {
|
||||
@ -1267,7 +1237,7 @@ ${commandList}`,
|
||||
.command('update')
|
||||
.description('Show manual upgrade commands (no auto-install)')
|
||||
.action(async () => {
|
||||
logEvent('tengu_update_check', {})
|
||||
|
||||
console.log(`Current version: ${MACRO.VERSION}`)
|
||||
console.log('Checking for updates...')
|
||||
|
||||
@ -1306,7 +1276,7 @@ ${commandList}`,
|
||||
.option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
|
||||
.action(async (number, { cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_view_logs', { number: number?.toString() ?? '' })
|
||||
|
||||
const context: { unmount?: () => void } = {}
|
||||
;(async () => {
|
||||
const { render } = await import('ink')
|
||||
@ -1358,7 +1328,7 @@ ${commandList}`,
|
||||
let messages, date, forkNumber
|
||||
try {
|
||||
if (isNumber) {
|
||||
logEvent('tengu_resume', { number: number.toString() })
|
||||
|
||||
const log = logs[number]
|
||||
if (!log) {
|
||||
console.error('No conversation found at index', number)
|
||||
@ -1368,7 +1338,7 @@ ${commandList}`,
|
||||
;({ date, forkNumber } = log)
|
||||
} else {
|
||||
// Handle file path case
|
||||
logEvent('tengu_resume', { filePath: identifier })
|
||||
|
||||
if (!existsSync(identifier)) {
|
||||
console.error('File does not exist:', identifier)
|
||||
process.exit(1)
|
||||
@ -1438,7 +1408,7 @@ ${commandList}`,
|
||||
.option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
|
||||
.action(async (number, { cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_view_errors', { number: number?.toString() ?? '' })
|
||||
|
||||
const context: { unmount?: () => void } = {}
|
||||
;(async () => {
|
||||
const { render } = await import('ink')
|
||||
@ -1463,7 +1433,7 @@ ${commandList}`,
|
||||
.description('Get a value from context')
|
||||
.action(async (key, { cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_context_get', { key })
|
||||
|
||||
const context = omit(
|
||||
await getContext(),
|
||||
'codeStyle',
|
||||
@ -1479,7 +1449,7 @@ ${commandList}`,
|
||||
.option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
|
||||
.action(async (key, value, { cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_context_set', { key })
|
||||
|
||||
setContext(key, value)
|
||||
console.log(`Set context.${key} to "${value}"`)
|
||||
process.exit(0)
|
||||
@ -1491,7 +1461,7 @@ ${commandList}`,
|
||||
.option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
|
||||
.action(async ({ cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_context_list', {})
|
||||
|
||||
const context = omit(
|
||||
await getContext(),
|
||||
'codeStyle',
|
||||
@ -1508,7 +1478,7 @@ ${commandList}`,
|
||||
.option('-c, --cwd <cwd>', 'The current working directory', String, cwd())
|
||||
.action(async (key, { cwd }) => {
|
||||
await setup(cwd, false)
|
||||
logEvent('tengu_context_delete', { key })
|
||||
|
||||
removeContext(key)
|
||||
console.log(`Removed context.${key}`)
|
||||
process.exit(0)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { hasPermissionsToUseTool } from '../permissions'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { BashTool, inputSchema } from '../tools/BashTool/BashTool'
|
||||
import { getCommandSubcommandPrefix } from '../utils/commands'
|
||||
import { REJECT_MESSAGE } from '../utils/messages'
|
||||
@ -25,12 +24,7 @@ function useCanUseTool(
|
||||
return useCallback<CanUseToolFn>(
|
||||
async (tool, input, toolUseContext, assistantMessage) => {
|
||||
return new Promise(resolve => {
|
||||
function logCancelledEvent() {
|
||||
logEvent('tengu_tool_use_cancelled', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
}
|
||||
function logCancelledEvent() {}
|
||||
|
||||
function resolveWithCancelledAndAbortAllToolCalls() {
|
||||
resolve({
|
||||
@ -58,10 +52,7 @@ function useCanUseTool(
|
||||
.then(async result => {
|
||||
// Has permissions to use tool, granted in config
|
||||
if (result.result) {
|
||||
logEvent('tengu_tool_use_granted_in_config', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
|
||||
resolve({ result: true })
|
||||
return
|
||||
}
|
||||
@ -92,31 +83,15 @@ function useCanUseTool(
|
||||
riskScore: null,
|
||||
onAbort() {
|
||||
logCancelledEvent()
|
||||
logEvent('tengu_tool_use_rejected_in_prompt', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
resolveWithCancelledAndAbortAllToolCalls()
|
||||
},
|
||||
onAllow(type) {
|
||||
if (type === 'permanent') {
|
||||
logEvent('tengu_tool_use_granted_in_prompt_permanent', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
} else {
|
||||
logEvent('tengu_tool_use_granted_in_prompt_temporary', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
}
|
||||
resolve({ result: true })
|
||||
},
|
||||
onReject() {
|
||||
logEvent('tengu_tool_use_rejected_in_prompt', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
resolveWithCancelledAndAbortAllToolCalls()
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useInput } from 'ink'
|
||||
import { ToolUseConfirm } from '../components/permissions/PermissionRequest'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { BinaryFeedbackContext } from '../screens/REPL'
|
||||
import type { SetToolJSXFn } from '../Tool'
|
||||
|
||||
@ -30,7 +29,7 @@ export function useCancelRequest(
|
||||
// Esc closes the message selector
|
||||
return
|
||||
}
|
||||
logEvent('tengu_cancel', {})
|
||||
|
||||
setToolJSX(null)
|
||||
setToolUseConfirm(null)
|
||||
setBinaryFeedbackContext(null)
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { useEffect } from 'react'
|
||||
import { logEvent } from '../services/statsig'
|
||||
|
||||
|
||||
export function useLogStartupTime(): void {
|
||||
useEffect(() => {
|
||||
const startupTimeMs = Math.round(process.uptime() * 1000)
|
||||
logEvent('tengu_timer', {
|
||||
event: 'startup',
|
||||
durationMs: String(startupTimeMs),
|
||||
})
|
||||
}, [])
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import { logEvent } from '../services/statsig'
|
||||
|
||||
import { logUnaryEvent, CompletionType } from '../utils/unaryLogging'
|
||||
import { ToolUseConfirm } from '../components/permissions/PermissionRequest'
|
||||
import { env } from '../utils/env'
|
||||
@ -19,11 +19,7 @@ export function usePermissionRequestLogging(
|
||||
unaryEvent: UnaryEvent,
|
||||
): void {
|
||||
useEffect(() => {
|
||||
// Log Statsig event
|
||||
logEvent('tengu_tool_use_show_permission_request', {
|
||||
messageID: toolUseConfirm.assistantMessage.message.id,
|
||||
toolName: toolUseConfirm.tool.name,
|
||||
})
|
||||
|
||||
|
||||
// Handle string or Promise language name
|
||||
const languagePromise = Promise.resolve(unaryEvent.language_name)
|
||||
|
||||
49
src/query.ts
49
src/query.ts
@ -16,7 +16,6 @@ import {
|
||||
queryModel,
|
||||
} from './services/claude.js'
|
||||
import { emitReminderEvent } from './services/systemReminder'
|
||||
import { logEvent } from './services/statsig'
|
||||
import { all } from './utils/generators'
|
||||
import { logError } from './utils/log'
|
||||
import {
|
||||
@ -415,10 +414,7 @@ export async function* runToolUse(
|
||||
)
|
||||
|
||||
|
||||
logEvent('tengu_tool_use_start', {
|
||||
toolName: toolUse.name,
|
||||
toolUseID: toolUse.id,
|
||||
})
|
||||
|
||||
|
||||
const toolName = toolUse.name
|
||||
const tool = toolUseContext.options.tools.find(t => t.name === toolName)
|
||||
@ -432,12 +428,7 @@ export async function* runToolUse(
|
||||
requestId: currentRequest?.id,
|
||||
})
|
||||
|
||||
logEvent('tengu_tool_use_error', {
|
||||
error: `No such tool available: ${toolName}`,
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName,
|
||||
toolUseID: toolUse.id,
|
||||
})
|
||||
|
||||
|
||||
yield createUserMessage([
|
||||
{
|
||||
@ -469,10 +460,7 @@ export async function* runToolUse(
|
||||
requestId: currentRequest?.id,
|
||||
})
|
||||
|
||||
logEvent('tengu_tool_use_cancelled', {
|
||||
toolName: tool.name,
|
||||
toolUseID: toolUse.id,
|
||||
})
|
||||
|
||||
|
||||
const message = createUserMessage([
|
||||
createToolResultStopMessage(toolUse.id),
|
||||
@ -579,12 +567,7 @@ async function* checkPermissionsAndCallTool(
|
||||
errorMessage = `Error: The View tool requires a 'file_path' parameter to specify which file to read. Please provide the absolute path to the file you want to view. For example: {"file_path": "/path/to/file.txt"}`
|
||||
}
|
||||
|
||||
logEvent('tengu_tool_use_error', {
|
||||
error: errorMessage,
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
toolInput: JSON.stringify(input).slice(0, 200),
|
||||
})
|
||||
|
||||
yield createUserMessage([
|
||||
{
|
||||
type: 'tool_result',
|
||||
@ -604,13 +587,6 @@ async function* checkPermissionsAndCallTool(
|
||||
context,
|
||||
)
|
||||
if (isValidCall?.result === false) {
|
||||
logEvent('tengu_tool_use_error', {
|
||||
error: isValidCall?.message.slice(0, 2000),
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
toolInput: JSON.stringify(input).slice(0, 200),
|
||||
...(isValidCall?.meta ?? {}),
|
||||
})
|
||||
yield createUserMessage([
|
||||
{
|
||||
type: 'tool_result',
|
||||
@ -645,10 +621,7 @@ async function* checkPermissionsAndCallTool(
|
||||
for await (const result of generator) {
|
||||
switch (result.type) {
|
||||
case 'result':
|
||||
logEvent('tengu_tool_use_success', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
|
||||
yield createUserMessage(
|
||||
[
|
||||
{
|
||||
@ -664,10 +637,7 @@ async function* checkPermissionsAndCallTool(
|
||||
)
|
||||
return
|
||||
case 'progress':
|
||||
logEvent('tengu_tool_use_progress', {
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
})
|
||||
|
||||
yield createProgressMessage(
|
||||
toolUseID,
|
||||
siblingToolUseIDs,
|
||||
@ -681,12 +651,7 @@ async function* checkPermissionsAndCallTool(
|
||||
} catch (error) {
|
||||
const content = formatError(error)
|
||||
logError(error)
|
||||
logEvent('tengu_tool_use_error', {
|
||||
error: content.slice(0, 2000),
|
||||
messageID: assistantMessage.message.id,
|
||||
toolName: tool.name,
|
||||
toolInput: JSON.stringify(input).slice(0, 1000),
|
||||
})
|
||||
|
||||
yield createUserMessage([
|
||||
{
|
||||
type: 'tool_result',
|
||||
|
||||
@ -45,7 +45,6 @@ import type { Tool } from '../Tool'
|
||||
// Auto-updater removed; only show a new version banner passed from CLI
|
||||
import { getGlobalConfig, saveGlobalConfig } from '../utils/config'
|
||||
import { MACRO } from '../constants/macros'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { getNextAvailableLogForkNumber } from '../utils/log'
|
||||
import {
|
||||
getErroredToolUseMessages,
|
||||
@ -214,7 +213,7 @@ export function REPL({
|
||||
useEffect(() => {
|
||||
const totalCost = getTotalCost()
|
||||
if (totalCost >= 5 /* $5 */ && !showCostDialog && !haveShownCostDialog) {
|
||||
logEvent('tengu_cost_threshold_reached', {})
|
||||
|
||||
setShowCostDialog(true)
|
||||
}
|
||||
}, [messages, showCostDialog, haveShownCostDialog])
|
||||
@ -681,7 +680,7 @@ export function REPL({
|
||||
...projectConfig,
|
||||
hasAcknowledgedCostThreshold: true,
|
||||
})
|
||||
logEvent('tengu_cost_threshold_acknowledged', {})
|
||||
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -25,7 +25,6 @@ import {
|
||||
normalizeContentFromAPI,
|
||||
} from '../utils/messages'
|
||||
import { countTokens } from '../utils/tokens'
|
||||
import { logEvent } from './statsig'
|
||||
import { withVCR } from './vcr'
|
||||
import {
|
||||
debug as debugLogger,
|
||||
@ -333,13 +332,7 @@ async function withRetry<T>(
|
||||
` ⎿ ${chalk.red(`API ${error.name} (${error.message}) · Retrying in ${Math.round(delayMs / 1000)} seconds… (attempt ${attempt}/${maxRetries})`)}`,
|
||||
)
|
||||
|
||||
logEvent('tengu_api_retry', {
|
||||
attempt: String(attempt),
|
||||
delayMs: String(delayMs),
|
||||
error: error.message,
|
||||
status: String(error.status),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
|
||||
|
||||
try {
|
||||
await abortableDelay(delayMs, options.signal)
|
||||
@ -770,9 +763,6 @@ function convertOpenAIResponseToAnthropic(response: OpenAI.ChatCompletion, tools
|
||||
let contentBlocks: ContentBlock[] = []
|
||||
const message = response.choices?.[0]?.message
|
||||
if (!message) {
|
||||
logEvent('weird_response', {
|
||||
response: JSON.stringify(response),
|
||||
})
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: [],
|
||||
@ -1426,13 +1416,6 @@ async function queryAnthropicNative(
|
||||
if (options?.prependCLISysprompt) {
|
||||
// Log stats about first block for analyzing prefix matching config
|
||||
const [firstSyspromptBlock] = splitSysPromptPrefix(systemPrompt)
|
||||
logEvent('tengu_sysprompt_block', {
|
||||
snippet: firstSyspromptBlock?.slice(0, 20),
|
||||
length: String(firstSyspromptBlock?.length ?? 0),
|
||||
hash: firstSyspromptBlock
|
||||
? createHash('sha256').update(firstSyspromptBlock).digest('hex')
|
||||
: '',
|
||||
})
|
||||
|
||||
systemPrompt = [getCLISyspromptPrefix(), ...systemPrompt]
|
||||
}
|
||||
@ -1750,17 +1733,7 @@ async function queryAnthropicNative(
|
||||
assistantMessage.costUSD = costUSD
|
||||
addToTotalCost(costUSD, durationMs)
|
||||
|
||||
logEvent('api_response_anthropic_native', {
|
||||
model,
|
||||
input_tokens: String(inputTokens),
|
||||
output_tokens: String(outputTokens),
|
||||
cache_creation_input_tokens: String(cacheCreationInputTokens),
|
||||
cache_read_input_tokens: String(cacheReadInputTokens),
|
||||
cost_usd: String(costUSD),
|
||||
duration_ms: String(durationMs),
|
||||
ttft_ms: String(ttftMs),
|
||||
attempt_number: String(attemptNumber),
|
||||
})
|
||||
|
||||
|
||||
return assistantMessage
|
||||
} catch (error) {
|
||||
@ -1850,13 +1823,6 @@ async function queryOpenAI(
|
||||
if (options?.prependCLISysprompt) {
|
||||
// Log stats about first block for analyzing prefix matching config (see https://console.statsig.com/4aF3Ewatb6xPVpCwxb5nA3/dynamic_configs/claude_cli_system_prompt_prefixes)
|
||||
const [firstSyspromptBlock] = splitSysPromptPrefix(systemPrompt)
|
||||
logEvent('tengu_sysprompt_block', {
|
||||
snippet: firstSyspromptBlock?.slice(0, 20),
|
||||
length: String(firstSyspromptBlock?.length ?? 0),
|
||||
hash: firstSyspromptBlock
|
||||
? createHash('sha256').update(firstSyspromptBlock).digest('hex')
|
||||
: '',
|
||||
})
|
||||
|
||||
systemPrompt = [getCLISyspromptPrefix() + systemPrompt] // some openai-like providers need the entire system prompt as a single block
|
||||
}
|
||||
@ -1943,9 +1909,6 @@ async function queryOpenAI(
|
||||
}
|
||||
const reasoningEffort = await getReasoningEffort(modelProfile, messages)
|
||||
if (reasoningEffort) {
|
||||
logEvent('debug_reasoning_effort', {
|
||||
effort: reasoningEffort,
|
||||
})
|
||||
opts.reasoning_effort = reasoningEffort
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import { memoize } from 'lodash-es'
|
||||
import type { MessageParam } from '@anthropic-ai/sdk/resources/index.mjs'
|
||||
import type { Command } from '../commands'
|
||||
import { getCwd } from '../utils/state'
|
||||
import { logEvent } from './statsig'
|
||||
import { execFile } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
|
||||
@ -559,12 +558,7 @@ export const loadCustomCommands = memoize(
|
||||
|
||||
// Log performance metrics for monitoring
|
||||
// This follows the same pattern as other performance-sensitive operations
|
||||
logEvent('tengu_custom_command_scan', {
|
||||
durationMs: duration.toString(),
|
||||
projectFilesFound: projectFiles.length.toString(),
|
||||
userFilesFound: userFiles.length.toString(),
|
||||
totalFiles: allFiles.length.toString(),
|
||||
})
|
||||
|
||||
|
||||
// Parse files and create command objects
|
||||
const commands: CustomCommandWithScope[] = []
|
||||
@ -619,12 +613,7 @@ export const loadCustomCommands = memoize(
|
||||
const enabledCommands = commands.filter(cmd => cmd.isEnabled)
|
||||
|
||||
// Log loading results for debugging and monitoring
|
||||
logEvent('tengu_custom_commands_loaded', {
|
||||
totalCommands: commands.length.toString(),
|
||||
enabledCommands: enabledCommands.length.toString(),
|
||||
userCommands: commands.filter(cmd => cmd.scope === 'user').length.toString(),
|
||||
projectCommands: commands.filter(cmd => cmd.scope === 'project').length.toString(),
|
||||
})
|
||||
|
||||
|
||||
return enabledCommands
|
||||
} catch (error) {
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
systemReminderService,
|
||||
} from '../services/systemReminder'
|
||||
import { getAgentFilePath } from '../utils/agentStorage'
|
||||
import { logEvent } from '../services/statsig'
|
||||
|
||||
interface FileTimestamp {
|
||||
path: string
|
||||
@ -44,11 +43,7 @@ class FileFreshnessService {
|
||||
// Reset session state on startup
|
||||
this.resetSession()
|
||||
|
||||
// Log session startup
|
||||
logEvent('file_freshness_session_startup', {
|
||||
agentId: context.agentId || 'default',
|
||||
timestamp: context.timestamp,
|
||||
})
|
||||
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@ -36,7 +36,6 @@ import type { Tool } from '../Tool'
|
||||
import { MCPTool } from '../tools/MCPTool/MCPTool'
|
||||
import { logMCPError } from '../utils/log'
|
||||
import { Command } from '../commands'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { PRODUCT_COMMAND } from '../constants/product.js'
|
||||
|
||||
type McpName = string
|
||||
@ -332,10 +331,8 @@ export const getClients = memoize(async (): Promise<WrappedClient[]> => {
|
||||
Object.entries(allServers).map(async ([name, serverRef]) => {
|
||||
try {
|
||||
const client = await connectToServer(name, serverRef as McpServerConfig)
|
||||
logEvent('tengu_mcp_server_connection_succeeded', {})
|
||||
return { name, client, type: 'connected' as const }
|
||||
} catch (error) {
|
||||
logEvent('tengu_mcp_server_connection_failed', {})
|
||||
logMCPError(
|
||||
name,
|
||||
`Connection failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
||||
@ -5,7 +5,6 @@ import * as url from 'url'
|
||||
|
||||
import { OAUTH_CONFIG } from '../constants/oauth'
|
||||
import { openBrowser } from '../utils/browser'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { logError } from '../utils/log'
|
||||
import { resetAnthropicClient } from './claude'
|
||||
import {
|
||||
@ -180,8 +179,7 @@ export class OAuthService {
|
||||
})
|
||||
res.end()
|
||||
|
||||
// Track which path the user is taking (automatic browser redirect)
|
||||
logEvent('tengu_oauth_automatic_redirect', {})
|
||||
|
||||
|
||||
this.processCallback({
|
||||
authorizationCode,
|
||||
@ -309,11 +307,7 @@ export async function createAndStoreApiKey(
|
||||
errorText = await createApiKeyResp.text()
|
||||
}
|
||||
|
||||
logEvent('tengu_oauth_api_key', {
|
||||
status: createApiKeyResp.ok ? 'success' : 'failure',
|
||||
statusCode: createApiKeyResp.status.toString(),
|
||||
error: createApiKeyResp.ok ? '' : errorText || JSON.stringify(apiKeyData),
|
||||
})
|
||||
|
||||
|
||||
if (createApiKeyResp.ok && apiKeyData && apiKeyData.raw_key) {
|
||||
const apiKey = apiKeyData.raw_key
|
||||
@ -347,11 +341,7 @@ export async function createAndStoreApiKey(
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
logEvent('tengu_oauth_api_key', {
|
||||
status: 'failure',
|
||||
statusCode: 'exception',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import { OpenAI } from 'openai'
|
||||
import { getGlobalConfig, GlobalConfig } from '../utils/config'
|
||||
import { ProxyAgent, fetch, Response } from 'undici'
|
||||
import { setSessionState, getSessionState } from '../utils/sessionState'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { debug as debugLogger, getCurrentRequest, logAPIError } from '../utils/debugLogger'
|
||||
|
||||
/**
|
||||
@ -169,12 +168,7 @@ const ERROR_HANDLERS: ErrorHandler[] = [
|
||||
remainder += line + '\n'
|
||||
}
|
||||
}
|
||||
logEvent('truncated_tool_description', {
|
||||
name: tool.function.name,
|
||||
original_length: String(tool.function.description.length),
|
||||
truncated_length: String(str.length),
|
||||
remainder_length: String(remainder.length),
|
||||
})
|
||||
|
||||
tool.function.description = str
|
||||
toolDescriptions[tool.function.name] = remainder
|
||||
}
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
import { memoize } from 'lodash-es'
|
||||
import chalk from 'chalk'
|
||||
// Statsig is disabled by default in the CLI runtime to avoid
|
||||
// bringing browser-only globals (e.g., XMLHttpRequest) into Node.
|
||||
// The Client SDK is browser-oriented; using it at module scope can
|
||||
// break on Windows shells. We keep a lightweight no-op shim.
|
||||
import { env } from '../utils/env'
|
||||
const gateValues: Record<string, boolean> = {}
|
||||
let client: any | null = null
|
||||
|
||||
export const initializeStatsig = memoize(
|
||||
async (): Promise<any | null> => {
|
||||
// Fully disabled in CLI by default
|
||||
return null
|
||||
},
|
||||
)
|
||||
|
||||
export function logEvent(
|
||||
eventName: string,
|
||||
metadata: { [key: string]: string | undefined },
|
||||
): void {
|
||||
// console.log('logEvent', eventName, metadata)
|
||||
if (env.isCI || process.env.NODE_ENV === 'test') {
|
||||
return
|
||||
}
|
||||
// Keep debug line for local visibility, but do not import client SDK
|
||||
if (process.argv.includes('--debug') || process.argv.includes('-d')) {
|
||||
console.log(chalk.dim(`[DEBUG-ONLY] Statsig event: ${eventName} ${JSON.stringify(metadata, null, 0)}`))
|
||||
}
|
||||
}
|
||||
|
||||
export const checkGate = memoize(async (gateName: string): Promise<boolean> => {
|
||||
// Default to disabled gates when Statsig is not active
|
||||
return false
|
||||
// if (env.isCI || process.env.NODE_ENV === 'test') {
|
||||
// return false
|
||||
// }
|
||||
// const statsigClient = await initializeStatsig()
|
||||
// if (!statsigClient) return false
|
||||
|
||||
// const value = statsigClient.checkGate(gateName)
|
||||
// gateValues[gateName] = value
|
||||
// return value
|
||||
})
|
||||
|
||||
export const useStatsigGate = (gateName: string, defaultValue = false) => {
|
||||
return false
|
||||
// const [gateValue, setGateValue] = React.useState(defaultValue)
|
||||
// React.useEffect(() => {
|
||||
// checkGate(gateName).then(setGateValue)
|
||||
// }, [gateName])
|
||||
// return gateValue
|
||||
}
|
||||
|
||||
export function getGateValues(): Record<string, boolean> {
|
||||
return { ...gateValues }
|
||||
}
|
||||
|
||||
export const getExperimentValue = memoize(
|
||||
async <T>(experimentName: string, defaultValue: T): Promise<T> => {
|
||||
return defaultValue
|
||||
// if (env.isCI || process.env.NODE_ENV === 'test') {
|
||||
// return defaultValue
|
||||
// }
|
||||
// const statsigClient = await initializeStatsig()
|
||||
// if (!statsigClient) return defaultValue
|
||||
|
||||
// const experiment = statsigClient.getExperiment(experimentName)
|
||||
// if (Object.keys(experiment.value).length === 0) {
|
||||
// logError(`getExperimentValue got empty value for ${experimentName}`)
|
||||
// return defaultValue
|
||||
// }
|
||||
// return experiment.value as T
|
||||
},
|
||||
)
|
||||
|
||||
// NB Not memoized like other methods, to allow for dynamic config changes
|
||||
export const getDynamicConfig = async <T>(
|
||||
configName: string,
|
||||
defaultValue: T,
|
||||
): Promise<T> => {
|
||||
return defaultValue
|
||||
// if (env.isCI || process.env.NODE_ENV === 'test') {
|
||||
// return defaultValue
|
||||
// }
|
||||
// const statsigClient = await initializeStatsig()
|
||||
// if (!statsigClient) return defaultValue
|
||||
|
||||
// const config = statsigClient.getDynamicConfig(configName)
|
||||
// if (Object.keys(config.value).length === 0) {
|
||||
// logError(`getDynamicConfig got empty value for ${configName}`)
|
||||
// return defaultValue
|
||||
// }
|
||||
// return config.value as T
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
import { StorageProvider } from '@statsig/client-core'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { homedir } from 'os'
|
||||
import { logError } from '../utils/log'
|
||||
import { existsSync, unlinkSync } from 'fs'
|
||||
import { CONFIG_BASE_DIR } from '../constants/product'
|
||||
|
||||
// Support both KODE_CONFIG_DIR and CLAUDE_CONFIG_DIR environment variables
|
||||
const CONFIG_DIR = process.env.KODE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR ?? path.join(homedir(), CONFIG_BASE_DIR)
|
||||
const STATSIG_DIR = path.join(CONFIG_DIR, 'statsig')
|
||||
|
||||
// Ensure the directory exists
|
||||
try {
|
||||
fs.mkdirSync(STATSIG_DIR, { recursive: true })
|
||||
} catch (error) {
|
||||
logError(`Failed to create statsig storage directory: ${error}`)
|
||||
}
|
||||
|
||||
export class FileSystemStorageProvider implements StorageProvider {
|
||||
private cache: Map<string, string> = new Map()
|
||||
private ready = false
|
||||
|
||||
constructor() {
|
||||
// Load all existing files into cache on startup
|
||||
try {
|
||||
if (!fs.existsSync(STATSIG_DIR)) {
|
||||
fs.mkdirSync(STATSIG_DIR, { recursive: true })
|
||||
}
|
||||
const files = fs.readdirSync(STATSIG_DIR)
|
||||
for (const file of files) {
|
||||
const key = decodeURIComponent(file)
|
||||
const value = fs.readFileSync(path.join(STATSIG_DIR, file), 'utf8')
|
||||
this.cache.set(key, value)
|
||||
}
|
||||
this.ready = true
|
||||
} catch (error) {
|
||||
logError(`Failed to initialize statsig storage: ${error}`)
|
||||
this.ready = true // Still mark as ready to avoid blocking
|
||||
}
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.ready
|
||||
}
|
||||
|
||||
isReadyResolver(): Promise<void> | null {
|
||||
return this.ready ? Promise.resolve() : null
|
||||
}
|
||||
|
||||
getProviderName(): string {
|
||||
return 'FileSystemStorageProvider'
|
||||
}
|
||||
|
||||
getItem(key: string): string | null {
|
||||
return this.cache.get(key) ?? null
|
||||
}
|
||||
|
||||
setItem(key: string, value: string): void {
|
||||
this.cache.set(key, value)
|
||||
try {
|
||||
const encodedKey = encodeURIComponent(key)
|
||||
fs.writeFileSync(path.join(STATSIG_DIR, encodedKey), value, 'utf8')
|
||||
} catch (error) {
|
||||
logError(`Failed to write statsig storage item: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
removeItem(key: string): void {
|
||||
this.cache.delete(key)
|
||||
const encodedKey = encodeURIComponent(key)
|
||||
const file = path.join(STATSIG_DIR, encodedKey)
|
||||
if (!existsSync(file)) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
unlinkSync(file)
|
||||
} catch (error) {
|
||||
logError(`Failed to remove statsig storage item: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
getAllKeys(): readonly string[] {
|
||||
return Array.from(this.cache.keys())
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import { getTodos, TodoItem } from '../utils/todoStorage'
|
||||
import { logEvent } from './statsig'
|
||||
|
||||
export interface ReminderMessage {
|
||||
role: 'system'
|
||||
@ -98,17 +97,7 @@ class SystemReminderService {
|
||||
}
|
||||
|
||||
// Log aggregated metrics instead of individual events for performance
|
||||
if (reminders.length > 0) {
|
||||
logEvent('system_reminder_batch', {
|
||||
count: reminders.length.toString(),
|
||||
types: reminders.map(r => r.type).join(','),
|
||||
priorities: reminders.map(r => r.priority).join(','),
|
||||
categories: reminders.map(r => r.category).join(','),
|
||||
sessionCount: this.sessionState.reminderCount.toString(),
|
||||
agentId: agentId || 'default',
|
||||
timestamp: currentTime.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return reminders
|
||||
}
|
||||
@ -339,13 +328,7 @@ class SystemReminderService {
|
||||
this.sessionState.contextPresent =
|
||||
Object.keys(context.context || {}).length > 0
|
||||
|
||||
// Log session startup
|
||||
logEvent('system_reminder_session_startup', {
|
||||
agentId: context.agentId || 'default',
|
||||
contextKeys: Object.keys(context.context || {}).join(','),
|
||||
messageCount: (context.messages || 0).toString(),
|
||||
timestamp: context.timestamp.toString(),
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// Todo change events
|
||||
|
||||
@ -17,7 +17,6 @@ import { getModelManager } from '../../utils/model'
|
||||
import BashToolResultMessage from './BashToolResultMessage'
|
||||
import { BANNED_COMMANDS, PROMPT } from './prompt'
|
||||
import { formatOutput, getCommandFilePaths } from './utils'
|
||||
import { logEvent } from '../../services/statsig'
|
||||
|
||||
export const inputSchema = z.strictObject({
|
||||
command: z.string().describe('The command to execute'),
|
||||
@ -176,7 +175,7 @@ export const BashTool = {
|
||||
// Shell directory is outside original working directory, reset it
|
||||
await PersistentShell.getInstance().setCwd(getOriginalCwd())
|
||||
stderr = `${stderr.trim()}${EOL}Shell cwd was reset to ${getOriginalCwd()}`
|
||||
logEvent('bash_tool_reset_to_original_dir', {})
|
||||
|
||||
}
|
||||
|
||||
// Update read timestamps for any files referenced by the command
|
||||
|
||||
@ -7,7 +7,6 @@ import { z } from 'zod'
|
||||
import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdatedMessage'
|
||||
import { StructuredDiff } from '../../components/StructuredDiff'
|
||||
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
|
||||
import { logEvent } from '../../services/statsig'
|
||||
import { Tool, ValidationResult } from '../../Tool'
|
||||
import { intersperse } from '../../utils/array'
|
||||
import {
|
||||
@ -251,7 +250,6 @@ export const FileEditTool = {
|
||||
|
||||
// Log when editing CLAUDE.md
|
||||
if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {
|
||||
logEvent('tengu_write_claudemd', {})
|
||||
}
|
||||
|
||||
// Emit file edited event for system reminders
|
||||
|
||||
@ -9,7 +9,6 @@ import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdated
|
||||
import { HighlightedCode } from '../../components/HighlightedCode'
|
||||
import { StructuredDiff } from '../../components/StructuredDiff'
|
||||
import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage'
|
||||
import { logEvent } from '../../services/statsig'
|
||||
import type { Tool } from '../../Tool'
|
||||
import { intersperse } from '../../utils/array'
|
||||
import {
|
||||
@ -226,7 +225,6 @@ export const FileWriteTool = {
|
||||
|
||||
// Log when writing to CLAUDE.md
|
||||
if (fullFilePath.endsWith(`${sep}${PROJECT_FILE}`)) {
|
||||
logEvent('tengu_write_claudemd', {})
|
||||
}
|
||||
|
||||
// Emit file edited event for system reminders
|
||||
|
||||
@ -5,7 +5,6 @@ import * as React from 'react'
|
||||
import { z } from 'zod'
|
||||
import { FileEditToolUpdatedMessage } from '../../components/FileEditToolUpdatedMessage'
|
||||
import { StructuredDiff } from '../../components/StructuredDiff'
|
||||
import { logEvent } from '../../services/statsig'
|
||||
import { Tool, ValidationResult } from '../../Tool'
|
||||
import { intersperse } from '../../utils/array'
|
||||
import {
|
||||
@ -374,12 +373,7 @@ export const MultiEditTool = {
|
||||
}
|
||||
|
||||
// Log the operation
|
||||
logEvent('multi_edit_tool_used', {
|
||||
file_path: relativePath,
|
||||
edits_count: String(edits.length),
|
||||
was_new_file: String(!fileExists),
|
||||
duration_ms: String(Date.now() - startTime),
|
||||
})
|
||||
|
||||
|
||||
yield {
|
||||
type: 'result',
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
StickerRequestForm,
|
||||
FormData,
|
||||
} from '../../components/StickerRequestForm'
|
||||
import { checkGate, logEvent } from '../../services/statsig'
|
||||
// Telemetry and gates removed
|
||||
import { getTheme } from '../../utils/theme'
|
||||
|
||||
const stickerRequestSchema = z.object({
|
||||
@ -19,18 +19,14 @@ export const StickerRequestTool: Tool = {
|
||||
userFacingName: () => 'Stickers',
|
||||
description: async () => DESCRIPTION,
|
||||
inputSchema: stickerRequestSchema,
|
||||
isEnabled: async () => {
|
||||
const enabled = await checkGate('tengu_sticker_easter_egg')
|
||||
return enabled
|
||||
},
|
||||
isEnabled: async () => false,
|
||||
isReadOnly: () => false,
|
||||
isConcurrencySafe: () => false, // StickerRequestTool modifies state, not safe for concurrent execution
|
||||
needsPermissions: () => false,
|
||||
prompt: async () => PROMPT,
|
||||
|
||||
async *call(_, context: ToolUseContext) {
|
||||
// Log form entry event
|
||||
logEvent('sticker_request_form_opened', {})
|
||||
|
||||
|
||||
// Create a promise to track form completion and status
|
||||
let resolveForm: (success: boolean) => void
|
||||
@ -45,19 +41,14 @@ export const StickerRequestTool: Tool = {
|
||||
jsx: (
|
||||
<StickerRequestForm
|
||||
onSubmit={(formData: FormData) => {
|
||||
// Log successful completion with form data
|
||||
logEvent('sticker_request_form_completed', {
|
||||
has_address: Boolean(formData.address1).toString(),
|
||||
has_optional_address: Boolean(formData.address2).toString(),
|
||||
})
|
||||
|
||||
resolveForm(true)
|
||||
if (extendedContext.setToolJSX) {
|
||||
extendedContext.setToolJSX(null) // Clear the JSX
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
// Log form cancellation
|
||||
logEvent('sticker_request_form_cancelled', {})
|
||||
|
||||
resolveForm(false)
|
||||
if (extendedContext.setToolJSX) {
|
||||
extendedContext.setToolJSX(null) // Clear the JSX
|
||||
|
||||
@ -5,7 +5,7 @@ import { Tool } from '../../Tool'
|
||||
import { DESCRIPTION, PROMPT } from './prompt'
|
||||
import { getTheme } from '../../utils/theme'
|
||||
import { MessageResponse } from '../../components/MessageResponse'
|
||||
import { checkGate, logEvent } from '../../services/statsig'
|
||||
// Telemetry and gates removed
|
||||
import { USE_BEDROCK, USE_VERTEX } from '../../utils/model'
|
||||
|
||||
const thinkToolSchema = z.object({
|
||||
@ -17,20 +17,14 @@ export const ThinkTool = {
|
||||
userFacingName: () => 'Think',
|
||||
description: async () => DESCRIPTION,
|
||||
inputSchema: thinkToolSchema,
|
||||
isEnabled: async () =>
|
||||
Boolean(process.env.THINK_TOOL) && (await checkGate('tengu_think_tool')),
|
||||
isEnabled: async () => Boolean(process.env.THINK_TOOL),
|
||||
isReadOnly: () => true,
|
||||
isConcurrencySafe: () => true, // ThinkTool is read-only, safe for concurrent execution
|
||||
needsPermissions: () => false,
|
||||
prompt: async () => PROMPT,
|
||||
|
||||
async *call(input, { messageId }) {
|
||||
logEvent('tengu_thinking', {
|
||||
messageId,
|
||||
thoughtLength: input.thought.length.toString(),
|
||||
method: 'tool',
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
|
||||
|
||||
yield {
|
||||
type: 'result',
|
||||
|
||||
@ -6,7 +6,6 @@ import { spawn, execSync, type ChildProcess } from 'child_process'
|
||||
import { isAbsolute, resolve, join } from 'path'
|
||||
import { logError } from './log'
|
||||
import * as os from 'os'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { PRODUCT_COMMAND } from '../constants/product'
|
||||
|
||||
type ExecResult = {
|
||||
@ -236,10 +235,6 @@ export class PersistentShell {
|
||||
if (code) {
|
||||
// TODO: It would be nice to alert the user that shell crashed
|
||||
logError(`Shell exited with code ${code} and signal ${signal}`)
|
||||
logEvent('persistent_shell_exit', {
|
||||
code: code?.toString() || 'null',
|
||||
signal: signal || 'null',
|
||||
})
|
||||
}
|
||||
for (const file of [
|
||||
this.statusFile,
|
||||
@ -303,20 +298,13 @@ export class PersistentShell {
|
||||
.split('\n')
|
||||
.filter(Boolean) // Filter out empty strings
|
||||
|
||||
if (childPids.length > 0) {
|
||||
logEvent('persistent_shell_command_interrupted', {
|
||||
numChildProcesses: childPids.length.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
childPids.forEach(pid => {
|
||||
try {
|
||||
process.kill(Number(pid), 'SIGTERM')
|
||||
} catch (error) {
|
||||
logError(`Failed to kill process ${pid}: ${error}`)
|
||||
logEvent('persistent_shell_kill_process_error', {
|
||||
error: (error as Error).message.substring(0, 10),
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
@ -355,9 +343,7 @@ export class PersistentShell {
|
||||
|
||||
resolve(result)
|
||||
} catch (error) {
|
||||
logEvent('persistent_shell_command_error', {
|
||||
error: (error as Error).message.substring(0, 10),
|
||||
})
|
||||
|
||||
reject(error as Error)
|
||||
} finally {
|
||||
this.isExecuting = false
|
||||
@ -418,9 +404,7 @@ export class PersistentShell {
|
||||
// If there's a syntax error, return an error and log it
|
||||
const errorStr =
|
||||
typeof stderr === 'string' ? stderr : String(stderr || '')
|
||||
logEvent('persistent_shell_syntax_error', {
|
||||
error: errorStr.substring(0, 10),
|
||||
})
|
||||
|
||||
return Promise.resolve({
|
||||
stdout: '',
|
||||
stderr: errorStr,
|
||||
@ -486,10 +470,7 @@ export class PersistentShell {
|
||||
this.killChildren()
|
||||
code = SIGTERM_CODE
|
||||
stderr += (stderr ? '\n' : '') + 'Command execution timed out'
|
||||
logEvent('persistent_shell_command_timeout', {
|
||||
command: command.substring(0, 10),
|
||||
timeout: commandTimeout.toString(),
|
||||
})
|
||||
|
||||
}
|
||||
resolve({
|
||||
stdout,
|
||||
@ -515,10 +496,7 @@ export class PersistentShell {
|
||||
? error.message
|
||||
: String(error || 'Unknown error')
|
||||
logError(`Error in sendToShell: ${errorString}`)
|
||||
logEvent('persistent_shell_write_error', {
|
||||
error: errorString.substring(0, 100),
|
||||
command: command.substring(0, 30),
|
||||
})
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { execFileNoThrow } from './execFileNoThrow'
|
||||
import { logError } from './log'
|
||||
import { getDynamicConfig } from '../services/statsig'
|
||||
|
||||
import { lt, gt } from 'semver'
|
||||
import { MACRO } from '../constants/macros'
|
||||
import { PRODUCT_NAME } from '../constants/product'
|
||||
@ -14,10 +14,7 @@ export type VersionConfig = {
|
||||
// Ensure current version meets minimum supported version; exit if too old
|
||||
export async function assertMinVersion(): Promise<void> {
|
||||
try {
|
||||
const versionConfig = await getDynamicConfig<VersionConfig>(
|
||||
'tengu_version_config',
|
||||
{ minVersion: '0.0.0' },
|
||||
)
|
||||
const versionConfig: VersionConfig = { minVersion: '0.0.0' }
|
||||
if (versionConfig.minVersion && lt(MACRO.VERSION, versionConfig.minVersion)) {
|
||||
const suggestions = await getUpdateCommandSuggestions()
|
||||
// Intentionally minimal: caller may print its own message; we just exit
|
||||
@ -122,4 +119,3 @@ export async function checkAndNotifyUpdate(): Promise<void> {
|
||||
logError(`update-notify: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,20 +0,0 @@
|
||||
import { memoize } from 'lodash-es'
|
||||
import { checkGate } from '../services/statsig'
|
||||
import {
|
||||
GATE_TOKEN_EFFICIENT_TOOLS,
|
||||
BETA_HEADER_TOKEN_EFFICIENT_TOOLS,
|
||||
CLAUDE_CODE_20250219_BETA_HEADER,
|
||||
} from '../constants/betas.js'
|
||||
|
||||
export const getBetas = memoize(async (): Promise<string[]> => {
|
||||
const betaHeaders = [CLAUDE_CODE_20250219_BETA_HEADER]
|
||||
|
||||
if (process.env.USER_TYPE === 'ant' || process.env.SWE_BENCH) {
|
||||
const useTokenEfficientTools = await checkGate(GATE_TOKEN_EFFICIENT_TOOLS)
|
||||
if (useTokenEfficientTools) {
|
||||
betaHeaders.push(BETA_HEADER_TOKEN_EFFICIENT_TOOLS)
|
||||
}
|
||||
}
|
||||
|
||||
return betaHeaders
|
||||
})
|
||||
@ -6,8 +6,6 @@ import { GLOBAL_CLAUDE_FILE } from './env'
|
||||
import { getCwd } from './state'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { safeParseJSON } from './json'
|
||||
import { checkGate, logEvent } from '../services/statsig'
|
||||
import { GATE_USE_EXTERNAL_UPDATER } from '../constants/betas'
|
||||
import { ConfigParseError } from './errors'
|
||||
import type { ThemeNames } from './theme'
|
||||
import { debug as debugLogger } from './debugLogger'
|
||||
@ -474,10 +472,7 @@ export function saveCurrentProjectConfig(projectConfig: ProjectConfig): void {
|
||||
}
|
||||
|
||||
export async function isAutoUpdaterDisabled(): Promise<boolean> {
|
||||
const useExternalUpdater = await checkGate(GATE_USE_EXTERNAL_UPDATER)
|
||||
return (
|
||||
useExternalUpdater || getGlobalConfig().autoUpdaterStatus === 'disabled'
|
||||
)
|
||||
return getGlobalConfig().autoUpdaterStatus === 'disabled'
|
||||
}
|
||||
|
||||
export const TEST_MCPRC_CONFIG_FOR_TESTING: Record<string, McpServerConfig> = {}
|
||||
@ -523,9 +518,7 @@ export const getMcprcConfig = memoize(
|
||||
const mcprcContent = readFileSync(mcprcPath, 'utf-8')
|
||||
const config = safeParseJSON(mcprcContent)
|
||||
if (config && typeof config === 'object') {
|
||||
logEvent('tengu_mcprc_found', {
|
||||
numServers: Object.keys(config).length.toString(),
|
||||
})
|
||||
// Logging removed
|
||||
return config as Record<string, McpServerConfig>
|
||||
}
|
||||
} catch {
|
||||
@ -561,10 +554,7 @@ export function getOrCreateUserID(): string {
|
||||
}
|
||||
|
||||
export function getConfigForCLI(key: string, global: boolean): unknown {
|
||||
logEvent('tengu_config_get', {
|
||||
key,
|
||||
global: global?.toString() ?? 'false',
|
||||
})
|
||||
|
||||
if (global) {
|
||||
if (!isGlobalConfigKey(key)) {
|
||||
console.error(
|
||||
@ -589,10 +579,7 @@ export function setConfigForCLI(
|
||||
value: unknown,
|
||||
global: boolean,
|
||||
): void {
|
||||
logEvent('tengu_config_set', {
|
||||
key,
|
||||
global: global?.toString() ?? 'false',
|
||||
})
|
||||
|
||||
if (global) {
|
||||
if (!isGlobalConfigKey(key)) {
|
||||
console.error(
|
||||
@ -634,10 +621,7 @@ export function setConfigForCLI(
|
||||
}
|
||||
|
||||
export function deleteConfigForCLI(key: string, global: boolean): void {
|
||||
logEvent('tengu_config_delete', {
|
||||
key,
|
||||
global: global?.toString() ?? 'false',
|
||||
})
|
||||
|
||||
if (global) {
|
||||
if (!isGlobalConfigKey(key)) {
|
||||
console.error(
|
||||
@ -664,9 +648,7 @@ export function deleteConfigForCLI(key: string, global: boolean): void {
|
||||
export function listConfigForCLI(global: true): GlobalConfig
|
||||
export function listConfigForCLI(global: false): ProjectConfig
|
||||
export function listConfigForCLI(global: boolean): object {
|
||||
logEvent('tengu_config_list', {
|
||||
global: global?.toString() ?? 'false',
|
||||
})
|
||||
|
||||
if (global) {
|
||||
const currentConfig = pick(getGlobalConfig(), GLOBAL_CONFIG_KEYS)
|
||||
return currentConfig
|
||||
|
||||
@ -11,7 +11,6 @@ import { MalformedCommandError } from './errors'
|
||||
import { logError } from './log'
|
||||
import { resolve } from 'path'
|
||||
import { last, memoize } from 'lodash-es'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import type { SetToolJSXFn, Tool, ToolUseContext } from '../Tool'
|
||||
import { lastX } from '../utils/generators'
|
||||
import { NO_CONTENT_MESSAGE } from '../services/claude'
|
||||
@ -175,7 +174,7 @@ export async function processUserInput(
|
||||
): Promise<Message[]> {
|
||||
// Bash commands
|
||||
if (mode === 'bash') {
|
||||
logEvent('tengu_input_bash', {})
|
||||
|
||||
|
||||
const userMessage = createUserMessage(`<bash-input>${input}</bash-input>`)
|
||||
|
||||
@ -242,7 +241,7 @@ export async function processUserInput(
|
||||
}
|
||||
// Koding mode - special wrapper for display
|
||||
else if (mode === 'koding') {
|
||||
logEvent('tengu_input_koding', {})
|
||||
|
||||
|
||||
const userMessage = createUserMessage(
|
||||
`<koding-input>${input}</koding-input>`,
|
||||
@ -265,7 +264,7 @@ export async function processUserInput(
|
||||
commandName = commandName + ' (MCP)'
|
||||
}
|
||||
if (!commandName) {
|
||||
logEvent('tengu_input_slash_missing', { input })
|
||||
|
||||
return [
|
||||
createAssistantMessage('Commands are in the form `/command [args]`'),
|
||||
]
|
||||
@ -274,7 +273,7 @@ export async function processUserInput(
|
||||
// Check if it's a real command before processing
|
||||
if (!hasCommand(commandName, context.options.commands)) {
|
||||
// If not a real command, treat it as a regular user input
|
||||
logEvent('tengu_input_prompt', {})
|
||||
|
||||
return [createUserMessage(input)]
|
||||
}
|
||||
|
||||
@ -288,7 +287,7 @@ export async function processUserInput(
|
||||
|
||||
// Local JSX commands
|
||||
if (newMessages.length === 0) {
|
||||
logEvent('tengu_input_command', { input })
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
@ -300,23 +299,23 @@ export async function processUserInput(
|
||||
typeof newMessages[1]!.message.content === 'string' &&
|
||||
newMessages[1]!.message.content.startsWith('Unknown command:')
|
||||
) {
|
||||
logEvent('tengu_input_slash_invalid', { input })
|
||||
|
||||
return newMessages
|
||||
}
|
||||
|
||||
// User-Assistant pair (eg. local commands)
|
||||
if (newMessages.length === 2) {
|
||||
logEvent('tengu_input_command', { input })
|
||||
|
||||
return newMessages
|
||||
}
|
||||
|
||||
// A valid command
|
||||
logEvent('tengu_input_command', { input })
|
||||
|
||||
return newMessages
|
||||
}
|
||||
|
||||
// Regular user prompt
|
||||
logEvent('tengu_input_prompt', {})
|
||||
|
||||
|
||||
// Check if this is a Koding request that needs special handling
|
||||
const isKodingRequest = context.options?.isKodingRequest === true
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { memoize } from 'lodash-es'
|
||||
import { getDynamicConfig, getExperimentValue } from '../services/statsig'
|
||||
|
||||
import { logError } from './log'
|
||||
import {
|
||||
getGlobalConfig,
|
||||
@ -28,15 +28,7 @@ const DEFAULT_MODEL_CONFIG: ModelConfig = {
|
||||
* Relies on the built-in caching from StatsigClient
|
||||
*/
|
||||
async function getModelConfig(): Promise<ModelConfig> {
|
||||
try {
|
||||
return await getDynamicConfig<ModelConfig>(
|
||||
'tengu-capable-model-config',
|
||||
DEFAULT_MODEL_CONFIG,
|
||||
)
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
return DEFAULT_MODEL_CONFIG
|
||||
}
|
||||
return DEFAULT_MODEL_CONFIG
|
||||
}
|
||||
|
||||
export const getSlowAndCapableModel = memoize(async (): Promise<string> => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { logEvent } from '../services/statsig'
|
||||
|
||||
type SessionState = {
|
||||
modelErrors: Record<string, unknown>
|
||||
currentError: string | null
|
||||
@ -24,16 +24,8 @@ function setSessionState(
|
||||
value?: any,
|
||||
): void {
|
||||
if (typeof keyOrState === 'string') {
|
||||
logEvent('session_state_set', {
|
||||
key: keyOrState,
|
||||
value: JSON.stringify(value),
|
||||
})
|
||||
sessionState[keyOrState] = value
|
||||
} else {
|
||||
logEvent('session_state_set', {
|
||||
key: 'partial',
|
||||
value: JSON.stringify(keyOrState),
|
||||
})
|
||||
Object.assign(sessionState, keyOrState)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { last } from 'lodash-es'
|
||||
import type { Message } from '../query'
|
||||
import { logEvent } from '../services/statsig'
|
||||
import { getLastAssistantMessageId } from './messages'
|
||||
import { ThinkTool } from '../tools/ThinkTool/ThinkTool'
|
||||
import { USE_BEDROCK, USE_VERTEX, getModelManager } from './model'
|
||||
@ -10,22 +9,10 @@ export async function getMaxThinkingTokens(
|
||||
): Promise<number> {
|
||||
if (process.env.MAX_THINKING_TOKENS) {
|
||||
const tokens = parseInt(process.env.MAX_THINKING_TOKENS, 10)
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: tokens.toString(),
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return tokens
|
||||
}
|
||||
|
||||
if (await ThinkTool.isEnabled()) {
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '0',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -34,12 +21,6 @@ export async function getMaxThinkingTokens(
|
||||
lastMessage?.type !== 'user' ||
|
||||
typeof lastMessage.message.content !== 'string'
|
||||
) {
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '0',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -53,12 +34,6 @@ export async function getMaxThinkingTokens(
|
||||
content.includes('think very hard') ||
|
||||
content.includes('ultrathink')
|
||||
) {
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '31999',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 32_000 - 1
|
||||
}
|
||||
|
||||
@ -69,31 +44,13 @@ export async function getMaxThinkingTokens(
|
||||
content.includes('think more') ||
|
||||
content.includes('megathink')
|
||||
) {
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '10000',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 10_000
|
||||
}
|
||||
|
||||
if (content.includes('think')) {
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '4000',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 4_000
|
||||
}
|
||||
|
||||
logEvent('tengu_thinking', {
|
||||
method: 'scratchpad',
|
||||
tokenCount: '0',
|
||||
messageId: getLastAssistantMessageId(messages),
|
||||
provider: USE_BEDROCK ? 'bedrock' : USE_VERTEX ? 'vertex' : '1p',
|
||||
})
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { logEvent } from '../services/statsig'
|
||||
|
||||
export type CompletionType =
|
||||
| 'str_replace_single'
|
||||
@ -16,11 +15,5 @@ type LogEvent = {
|
||||
}
|
||||
|
||||
export function logUnaryEvent(event: LogEvent): void {
|
||||
logEvent('tengu_unary_event', {
|
||||
event: event.event,
|
||||
completion_type: event.completion_type,
|
||||
language_name: event.metadata.language_name,
|
||||
message_id: event.metadata.message_id,
|
||||
platform: event.metadata.platform,
|
||||
})
|
||||
// intentionally no-op
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { getGlobalConfig, getOrCreateUserID } from './config'
|
||||
import { memoize } from 'lodash-es'
|
||||
import { env } from './env'
|
||||
import { type StatsigUser } from '@statsig/js-client'
|
||||
import { execFileNoThrow } from './execFileNoThrow'
|
||||
import { logError, SESSION_ID } from './log'
|
||||
import { MACRO } from '../constants/macros'
|
||||
@ -14,7 +13,16 @@ export const getGitEmail = memoize(async (): Promise<string | undefined> => {
|
||||
return result.stdout.trim() || undefined
|
||||
})
|
||||
|
||||
export const getUser = memoize(async (): Promise<StatsigUser> => {
|
||||
type SimpleUser = {
|
||||
customIDs?: Record<string, string>
|
||||
userID: string
|
||||
appVersion?: string
|
||||
userAgent?: string
|
||||
email?: string
|
||||
custom?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export const getUser = memoize(async (): Promise<SimpleUser> => {
|
||||
const userID = getOrCreateUserID()
|
||||
const config = getGlobalConfig()
|
||||
const email = undefined
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user