/** * Probe the `select_tools` (progressive tool disclosure) capability of the * kimi-type providers, based on the real providers/auth in * `~/.kimi-code/config.toml`. * * Feature recap (from the Tool Select guide): with the `tool-select` * experimental flag + `tool_use` + `dynamically_loaded_tools` capabilities, * MCP tool schemas no longer ride the top-level `tools[]`; the model calls * the builtin `select_tools` with exact names (announced in * ``/`` system reminders), the engine injects * the loaded schemas as a `role: 'system'` message whose `tools` field * carries full definitions, and the model then calls the loaded tool. * * The wire fact that makes this kimi-specific: only the kimi vendor trait * serializes `message.tools` into the outbound request * (`kimiOpenAITrait.convertMessage`); every other base SKIPS * tool-declaration-only messages entirely. So the whole mechanism can only * work on `(kimi, openai)` — this example proves that on a stub, then * behaviorally tests each real kimi model with a simulated "kimi computer * use" tool pool. * * Part A — deterministic, local stub (OpenAI Chat Completions wire): * 1. kimi providerType: the tool-declaration message reaches the wire with * an embedded `tools` array. * 2. no providerType (plain openai): the same message is dropped — the * boundary non-kimi providers cannot cross. * 3. `deferred: true` top-level tools are filtered out of the wire * `tools[]` by the contract's generate() (their schema travels via the * declaration message instead). * * Part B — live, per kimi model from the real config: a two-step flow that * mirrors exactly what the agent loop produces. * step 1: announcement + `select_tools` available, user asks for a * screenshot — does the model call `select_tools`, with names from * the announced list? * step 2: history += the select_tools call, its "Loaded: ..." result, and * the schema-injection system message — does the model now call * the loaded computer-use tool? * * Run: * pnpm -C packages/klient exec tsx --tsconfig ./tsconfig.examples.json \ * --import ../../build/register-raw-text-loader.mjs examples/kimi-select-tools.ts * * Env: * KIMI_CODE_HOME — default `~/.kimi-code` * KIMI_SELECT_TOOLS_MODELS — comma-separated model ids for the live parts (default: all kimi-type) * KIMI_SELECT_TOOLS_SKIP_LIVE — set to `1` to skip part B (no real API calls) * KIMI_SELECT_TOOLS_TAP — set to `1` to run part C instead of B: route * the flow through a logging proxy and dump * the actual wire Context of each request. */ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'; import { homedir } from 'node:os'; import { join } from 'node:path'; import { EXAMPLE_CLIENT_IDENTITY } from './identity.js'; import type { AddressInfo } from 'node:net'; import { bootstrap, logSeed, resolveLoggingConfig } from '@moonshot-ai/agent-core-v2'; import { IConfigService } from '@moonshot-ai/agent-core-v2/app/config/config'; import { renderLoadableToolsAnnouncement } from '@moonshot-ai/agent-core-v2/agent/toolSelect/dynamicTools'; import { UNKNOWN_CAPABILITY } from '@moonshot-ai/agent-core-v2/llm-adapter/contract/capability'; import type { Message } from '@moonshot-ai/agent-core-v2/llm-adapter/contract/message'; import type { ToolDescription as Tool } from '@moonshot-ai/agent-core-v2/human/llm/message'; import { createStaticCredentialProvider } from '@moonshot-ai/agent-core-v2/human/credentials/credentials'; import type { Model } from '@moonshot-ai/agent-core-v2/llm-adapter/model/catalog'; import { IModelCatalog } from '@moonshot-ai/agent-core-v2/llm-adapter/model/catalog'; import type { ModelRequestInput, ModelRequester, } from '@moonshot-ai/agent-core-v2/llm-adapter/model/model-requester'; import { ModelRequesterImpl } from '@moonshot-ai/agent-core-v2/llm-adapter/model/model-requester-impl'; import { IProtocolAdapterRegistry } from '@moonshot-ai/agent-core-v2/llm-adapter/protocol/protocol'; import { ProtocolAdapterRegistry } from '@moonshot-ai/agent-core-v2/llm-adapter/protocol/protocolAdapterRegistry'; function assert(cond: boolean, message: string): asserts cond { if (!cond) throw new Error(`assertion failed: ${message}`); } // --------------------------------------------------------------------------- // The simulated "kimi computer use" MCP tool pool. // --------------------------------------------------------------------------- const COMPUTER_USE_TOOLS: readonly Tool[] = [ { name: 'computer_screenshot', description: 'Take a screenshot of the current screen and return it as an image.', parameters: { type: 'object', properties: {}, additionalProperties: false }, }, { name: 'computer_click', description: 'Click at a screen coordinate.', parameters: { type: 'object', properties: { x: { type: 'number', description: 'Screen x coordinate.' }, y: { type: 'number', description: 'Screen y coordinate.' }, button: { type: 'string', enum: ['left', 'right', 'middle'] }, }, required: ['x', 'y'], additionalProperties: false, }, }, { name: 'computer_move', description: 'Move the mouse to a screen coordinate.', parameters: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' }, }, required: ['x', 'y'], additionalProperties: false, }, }, { name: 'computer_type', description: 'Type text at the current focus.', parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'], additionalProperties: false, }, }, { name: 'computer_scroll', description: 'Scroll at a screen coordinate.', parameters: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' }, delta_y: { type: 'number' }, }, required: ['x', 'y', 'delta_y'], additionalProperties: false, }, }, ]; const COMPUTER_USE_NAMES = COMPUTER_USE_TOOLS.map((t) => t.name); /** The builtin select_tools schema, mirroring `SelectToolsInputSchema`. */ const SELECT_TOOLS: Tool = { name: 'select_tools', description: 'Load one or more tools by name so you can call them. ' + 'All available tool names are listed in the / announcements ' + 'in the system context — fold them in order to get the current list. ' + 'Pass the exact name(s) you need; their full definitions become available immediately, ' + 'so you can call them directly in your next tool call.', parameters: { type: 'object', properties: { names: { type: 'array', items: { type: 'string' }, minItems: 1, description: 'Exact tool names to load, taken from the latest announced tool list.', }, }, required: ['names'], additionalProperties: false, }, }; const SYSTEM_PROMPT = 'You are a computer-use agent operating the user\u2019s computer through tool calls. ' + 'Only a core tool set is available up front; additional tools are announced by name in ' + ' blocks in the system context. To use an announced tool, first call ' + 'select_tools with its exact name to load its definition, then call the tool itself.'; const userMessage = (text: string): Message => ({ role: 'user', content: [{ type: 'text', text }], toolCalls: [], }); /** The schema-injection message the engine appends after a successful load. */ const toolDeclarationMessage = (tools: readonly Tool[]): Message => ({ role: 'system', content: [], toolCalls: [], tools, }); const announcementMessage = (names: readonly string[]): Message => ({ role: 'system', content: [ { type: 'text', text: renderLoadableToolsAnnouncement(names, []) }, ], toolCalls: [], }); // --------------------------------------------------------------------------- // Shared request draining. // --------------------------------------------------------------------------- interface Collected { readonly text: string; readonly toolCalls: readonly { id: string; name: string; arguments: string | null }[]; readonly finishReason?: string | undefined; } async function collect( requester: ModelRequester, input: ModelRequestInput, signal?: AbortSignal, ): Promise { let text = ''; let toolCalls: Collected['toolCalls'] = []; let finishReason: string | undefined; for await (const event of requester.request(input, signal)) { if (event.type === 'part' && event.part.type === 'text') text += event.part.text; if (event.type === 'finish') { finishReason = event.providerFinishReason ?? event.rawFinishReason; toolCalls = event.message.toolCalls.map((t) => ({ id: t.id, name: t.name, arguments: t.arguments, })); } } return { text, toolCalls, finishReason }; } // --------------------------------------------------------------------------- // Part A — deterministic wire-encoding probes against a local stub. // --------------------------------------------------------------------------- const sseChunk = (delta: object, finishReason: string | null): string => JSON.stringify({ id: 'chatcmpl-probe', object: 'chat.completion.chunk', created: 0, model: 'probe-model', choices: [{ index: 0, delta, finish_reason: finishReason }], }); function writePong(res: ServerResponse): void { res.writeHead(200, { 'content-type': 'text/event-stream' }); res.write(`data: ${sseChunk({ role: 'assistant' }, null)}\n\n`); res.write(`data: ${sseChunk({ content: 'pong' }, null)}\n\n`); res.write(`data: ${sseChunk({}, 'stop')}\n\n`); res.end('data: [DONE]\n\n'); } interface WireMessage { readonly role?: string; readonly tools?: readonly { function?: { name?: string } }[]; } interface WireBody { readonly messages?: readonly WireMessage[]; readonly tools?: readonly { function?: { name?: string } }[]; } async function probeWireEncoding(): Promise { console.log('\n=== part A: wire-encoding boundary (local stub) ==='); let handler: (req: IncomingMessage, res: ServerResponse) => void = (_req, res) => writePong(res); let lastBody: WireBody | undefined; const server = createServer((req, res) => { const chunks: Buffer[] = []; req.on('data', (chunk: Buffer) => chunks.push(chunk)); req.on('end', () => { try { lastBody = JSON.parse(Buffer.concat(chunks).toString('utf8')) as WireBody; } catch { lastBody = undefined; } handler(req, res); }); }); await new Promise((resolve) => { server.listen(0, '127.0.0.1', () => resolve()); }); const port = (server.address() as AddressInfo).port; const registry = new ProtocolAdapterRegistry(); const makeRequester = (providerType?: string): ModelRequester => { const model: Model = { id: 'probe', name: 'probe-model', aliases: [], protocol: 'openai', baseUrl: `http://127.0.0.1:${String(port)}`, headers: {}, capabilities: { ...UNKNOWN_CAPABILITY, max_context_tokens: 8192 }, maxContextSize: 8192, alwaysThinking: false, providerType, providerName: providerType ?? 'probe', credentialProvider: createStaticCredentialProvider('sk-probe'), }; return new ModelRequesterImpl(model, registry); }; const input: ModelRequestInput = { systemPrompt: SYSTEM_PROMPT, tools: [SELECT_TOOLS, { ...COMPUTER_USE_TOOLS[0]!, deferred: true }], messages: [ announcementMessage(COMPUTER_USE_NAMES), userMessage('take a screenshot'), toolDeclarationMessage([COMPUTER_USE_TOOLS[0]!]), ], }; try { // 1) kimi providerType: the declaration message reaches the wire, with the // loaded schema embedded in the message — and the deferred top-level tool // does NOT duplicate into the wire tools[]. await collect(makeRequester('kimi'), input); const kimiBody = lastBody; const declaration = kimiBody?.messages?.find((m) => Array.isArray(m.tools)); assert(declaration !== undefined, 'kimi: a wire message carries the loaded tool schemas'); assert( declaration.tools?.some((t) => t.function?.name === 'computer_screenshot') === true, 'kimi: declaration embeds computer_screenshot', ); assert( kimiBody?.tools?.some((t) => t.function?.name === 'select_tools') === true, 'kimi: top-level tools[] keeps select_tools', ); assert( kimiBody?.tools?.some((t) => t.function?.name === 'computer_screenshot') !== true, 'kimi: deferred tool stays OUT of top-level tools[]', ); console.log( `[ok] kimi: declaration on wire (role=${String(declaration.role)}), ` + `top-level tools=[${(kimiBody?.tools ?? []).map((t) => t.function?.name ?? '?').join(', ')}]`, ); // 2) plain openai: the same declaration message is dropped outright — the // dynamic-tool schema can never reach a non-kimi wire. (3 wire messages = // system prompt + announcement + user; the declaration is gone.) await collect(makeRequester(), input); const plainBody = lastBody; assert( plainBody?.messages?.every((m) => !Array.isArray(m.tools)) === true, 'openai: declaration message dropped (no wire message carries tools)', ); assert( plainBody?.messages?.length === 3, `openai: declaration removed from history (got ${String(plainBody?.messages?.length)} messages)`, ); console.log( '[ok] openai: declaration message dropped — dynamic tools cannot cross a non-kimi wire', ); } finally { server.closeAllConnections(); await new Promise((resolve) => { server.close(() => resolve()); }); } } // --------------------------------------------------------------------------- // Part B — live two-step select_tools flow per real kimi model. // --------------------------------------------------------------------------- type Step1Outcome = | { readonly kind: 'selected'; readonly names: readonly string[]; readonly callId: string; readonly argumentsJson: string } | { readonly kind: 'no-call'; readonly text: string } | { readonly kind: 'error'; readonly message: string }; type Step2Outcome = | { readonly kind: 'called'; readonly name: string; readonly argumentsJson: string | null } | { readonly kind: 'other'; readonly description: string } | { readonly kind: 'error'; readonly message: string }; async function withTimeout(promise: Promise, ms: number, label: string): Promise { let timer: ReturnType | undefined; try { return await Promise.race([ promise, new Promise((_resolve, reject) => { timer = setTimeout(() => reject(new Error(`${label} timed out after ${String(ms)}ms`)), ms); }), ]); } finally { if (timer !== undefined) clearTimeout(timer); } } interface Scenario { readonly prompt: string; /** The tool a correct model is expected to select and call. */ readonly expectTool: string; } const SCENARIOS: readonly Scenario[] = [ { prompt: 'Take a screenshot of my screen right now.', expectTool: 'computer_screenshot' }, { prompt: 'Click at the exact center of my screen. The screen resolution is 1920x1080.', expectTool: 'computer_click', }, ]; async function step1Select(requester: ModelRequester, scenario: Scenario): Promise { try { const result = await withTimeout( collect(requester, { systemPrompt: SYSTEM_PROMPT, tools: [SELECT_TOOLS], messages: [ announcementMessage(COMPUTER_USE_NAMES), userMessage(scenario.prompt), ], }), 60_000, 'step1', ); const call = result.toolCalls.find((t) => t.name === 'select_tools'); if (call === undefined) { const other = result.toolCalls.map((t) => t.name).join(', '); return { kind: 'no-call', text: result.toolCalls.length > 0 ? `called [${other}] instead` : `answered text: ${result.text.slice(0, 80)}`, }; } let names: string[] = []; try { const parsed = JSON.parse(call.arguments ?? '{}') as { names?: unknown }; if (Array.isArray(parsed.names)) names = parsed.names.filter((n): n is string => typeof n === 'string'); } catch { // keep names empty — reported below } return { kind: 'selected', names, callId: call.id, argumentsJson: call.arguments ?? '' }; } catch (error) { return { kind: 'error', message: error instanceof Error ? error.message.split('\n')[0] ?? '' : String(error) }; } } async function step2UseLoadedTool( requester: ModelRequester, step1: Extract, scenario: Scenario, ): Promise { const validNames = step1.names.filter((n) => COMPUTER_USE_NAMES.includes(n)); const loadName = validNames.includes(scenario.expectTool) ? scenario.expectTool : validNames[0]; if (loadName === undefined) { return { kind: 'other', description: `selected names not in announced list: [${step1.names.join(', ')}]` }; } const loadedTool = COMPUTER_USE_TOOLS.find((t) => t.name === loadName)!; const messages = (withDeclaration: boolean): ModelRequestInput => ({ systemPrompt: SYSTEM_PROMPT, tools: [SELECT_TOOLS, { ...loadedTool, deferred: true }], messages: [ announcementMessage(COMPUTER_USE_NAMES), userMessage(scenario.prompt), { role: 'assistant', content: [], toolCalls: [ { type: 'function', id: step1.callId, name: 'select_tools', arguments: JSON.stringify({ names: [loadName] }), }, ], }, { role: 'tool', toolCallId: step1.callId, content: [{ type: 'text', text: `Loaded: ${loadName}` }], toolCalls: [], }, // The schema-injection message — the exact wire shape whose acceptance // this probe measures. Dropped entirely in the isolation retry below. ...(withDeclaration ? [toolDeclarationMessage([loadedTool])] : []), ], }); try { const result = await withTimeout(collect(requester, messages(true)), 60_000, 'step2'); const call = result.toolCalls.find((t) => t.name === loadName); if (call !== undefined) { return { kind: 'called', name: call.name, argumentsJson: call.arguments }; } const other = result.toolCalls.map((t) => t.name).join(', '); return { kind: 'other', description: result.toolCalls.length > 0 ? `called [${other}] instead of ${loadName}` : `answered text: ${result.text.slice(0, 80)}`, }; } catch (error) { const message = error instanceof Error ? error.message.split('\n')[0] ?? '' : String(error); // Differential: retry the SAME request minus the schema-injection message. // If it then succeeds, the backend rejects the dynamic-schema wire shape // specifically; if it fails too, the problem is elsewhere in the flow. try { await withTimeout(collect(requester, messages(false)), 60_000, 'step2-isolation'); return { kind: 'error', message: `${message} (isolation: same request WITHOUT the tools-in-message injection succeeds — backend rejects the dynamic-schema wire shape)`, }; } catch { return { kind: 'error', message: `${message} (isolation: also fails without the injection — not caused by the declaration message)` }; } } } async function probeLiveKimiProviders(): Promise { const homeDir = process.env['KIMI_CODE_HOME'] ?? join(homedir(), '.kimi-code'); console.log(`\n=== part B: live select_tools flow on real kimi providers (${homeDir}) ===`); const { app } = bootstrap({ homeDir, clientIdentity: EXAMPLE_CLIENT_IDENTITY }, [ ...logSeed(resolveLoggingConfig({ homeDir, env: process.env })), ]); try { await app.accessor.get(IConfigService).ready; const catalog = app.accessor.get(IModelCatalog); const filter = process.env['KIMI_SELECT_TOOLS_MODELS']?.split(',').map((s) => s.trim()); const models = await catalog.listModels(); const targets = models.filter((m) => { if (filter !== undefined && !filter.includes(m.model)) return false; try { return catalog.get(m.model).providerType === 'kimi'; } catch { return false; } }); assert(targets.length > 0, 'at least one kimi-type model configured'); const summary: string[] = []; for (const m of targets) { const model = catalog.get(m.model); const declared = model.capabilities.dynamically_loaded_tools === true; const requester = catalog.getRequester(m.model); for (const scenario of SCENARIOS) { const startedAt = Date.now(); const step1 = await step1Select(requester, scenario); let row: string; if (step1.kind === 'selected') { const step2 = await step2UseLoadedTool(requester, step1, scenario); const elapsed = `${String(Date.now() - startedAt)}ms`; if (step2.kind === 'called') { row = `PASS select=[${step1.names.join(', ')}] then called ${step2.name}(${step2.argumentsJson ?? ''})`; } else if (step2.kind === 'other') { row = `PARTIAL select=[${step1.names.join(', ')}] but step2: ${step2.description}`; } else { row = `PARTIAL select=[${step1.names.join(', ')}] but step2 error: ${step2.message}`; } console.log(`[${m.model}] ${scenario.expectTool} ${elapsed} declared=${String(declared)} ${row}`); } else if (step1.kind === 'no-call') { row = `FAIL no select_tools call: ${step1.text}`; console.log(`[${m.model}] ${scenario.expectTool} ${String(Date.now() - startedAt)}ms declared=${String(declared)} ${row}`); } else { row = `ERROR ${step1.message}`; console.log(`[${m.model}] ${scenario.expectTool} ${String(Date.now() - startedAt)}ms declared=${String(declared)} ${row}`); } summary.push(`${m.model.padEnd(40)} ${scenario.expectTool.padEnd(22)} ${row}`); } } console.log('\n=== live summary (declared = config declares dynamically_loaded_tools) ==='); for (const line of summary) console.log(line); } finally { app.dispose(); } } // --------------------------------------------------------------------------- // Part C — TAP mode: sit a logging proxy between the requester and the real // endpoint, and dump the actual wire Context (structure digest only — auth // headers are forwarded untouched, never printed). // --------------------------------------------------------------------------- interface TapWireTool { readonly type?: string; readonly function?: { readonly name?: string }; } interface TapWireMessage { readonly role?: string; readonly content?: unknown; readonly tools?: readonly TapWireTool[]; readonly tool_calls?: readonly { readonly function?: { readonly name?: string } }[]; readonly tool_call_id?: string; } function describeWireBody(raw: Buffer): string[] { const lines: string[] = []; let body: { tools?: TapWireTool[]; messages?: TapWireMessage[]; model?: string }; try { body = JSON.parse(raw.toString('utf8')) as typeof body; } catch { return [' (unparseable body)']; } const toolName = (t: TapWireTool): string => t.function?.name ?? t.type ?? '?'; lines.push(` model=${String(body.model)}`); lines.push(` top-level tools[] = [${(body.tools ?? []).map(toolName).join(', ')}]`); for (const [i, m] of (body.messages ?? []).entries()) { const parts: string[] = [`#${String(i)} role=${String(m.role)}`]; if (Array.isArray(m.tools)) { parts.push(`>>> tools=[${m.tools.map(toolName).join(', ')}] (dynamic schema injected HERE)`); } if (Array.isArray(m.tool_calls)) { parts.push(`tool_calls=[${m.tool_calls.map((tc) => tc.function?.name ?? '?').join(', ')}]`); } if (m.tool_call_id !== undefined) parts.push(`tool_call_id=${m.tool_call_id}`); const content = typeof m.content === 'string' ? m.content : m.content === undefined ? '' : JSON.stringify(m.content); if (content.length > 0) parts.push(`content=${content.slice(0, 60).replaceAll('\n', ' ')}…`); lines.push(` ${parts.join(' ')}`); } return lines; } async function probeTappedContext(): Promise { const homeDir = process.env['KIMI_CODE_HOME'] ?? join(homedir(), '.kimi-code'); console.log(`\n=== part C: tapped wire context (${homeDir}) ===`); const { app } = bootstrap({ homeDir, clientIdentity: EXAMPLE_CLIENT_IDENTITY }, [ ...logSeed(resolveLoggingConfig({ homeDir, env: process.env })), ]); try { await app.accessor.get(IConfigService).ready; const catalog = app.accessor.get(IModelCatalog); const registry = app.accessor.get(IProtocolAdapterRegistry); const filter = process.env['KIMI_SELECT_TOOLS_MODELS']?.split(',').map((s) => s.trim()); const models = await catalog.listModels(); const targets = models.filter((m) => { if (filter !== undefined && !filter.includes(m.model)) return false; try { return catalog.get(m.model).providerType === 'kimi'; } catch { return false; } }); assert(targets.length > 0, 'at least one kimi-type model configured'); for (const m of targets) { const model = catalog.get(m.model); if (model.baseUrl === undefined) { console.log(`[${m.model}] skip tap: model has no resolved baseUrl`); continue; } const upstream = model.baseUrl; let tapCount = 0; const server = createServer((req, res) => { const chunks: Buffer[] = []; req.on('data', (chunk: Buffer) => chunks.push(chunk)); req.on('end', () => { void (async () => { const raw = Buffer.concat(chunks); tapCount += 1; console.log(`\n[${m.model}] ── request #${String(tapCount)} → ${upstream}${req.url ?? ''}`); for (const line of describeWireBody(raw)) console.log(line); const headers = { ...(req.headers as Record) }; delete headers['host']; delete headers['content-length']; delete headers['connection']; const response = await fetch(`${upstream}${req.url ?? ''}`, { method: req.method, headers, body: raw.length > 0 ? raw : undefined, }); const passthrough: Record = {}; response.headers.forEach((value, key) => { if (key !== 'content-length' && key !== 'content-encoding' && key !== 'transfer-encoding') { passthrough[key] = value; } }); res.writeHead(response.status, passthrough); res.end(Buffer.from(await response.arrayBuffer())); })().catch((error: unknown) => { res.writeHead(502); res.end(String(error)); }); }); }); await new Promise((resolve) => { server.listen(0, '127.0.0.1', () => resolve()); }); const port = (server.address() as AddressInfo).port; try { const tapped = new ModelRequesterImpl( { ...model, baseUrl: `http://127.0.0.1:${String(port)}` }, registry, ); // One scenario is enough to show the mechanism: click (has arguments). const scenario = SCENARIOS[1]!; const step1 = await step1Select(tapped, scenario); if (step1.kind === 'selected') { const step2 = await step2UseLoadedTool(tapped, step1, scenario); console.log( `\n[${m.model}] outcome: select=[${step1.names.join(', ')}] -> ${step2.kind === 'called' ? `called ${step2.name}(${step2.argumentsJson ?? ''})` : JSON.stringify(step2)}`, ); } else { console.log(`\n[${m.model}] step1 outcome: ${JSON.stringify(step1)}`); } } finally { server.closeAllConnections(); await new Promise((resolve) => { server.close(() => resolve()); }); } } } finally { app.dispose(); } } // --------------------------------------------------------------------------- async function main(): Promise { await probeWireEncoding(); if (process.env['KIMI_SELECT_TOOLS_TAP'] === '1') { await probeTappedContext(); } else if (process.env['KIMI_SELECT_TOOLS_SKIP_LIVE'] !== '1') { await probeLiveKimiProviders(); } console.log('\nselect-tools: OK'); } try { await main(); } catch (error) { console.error(error); process.exit(1); }