const args = process.argv.slice(2) function getArg(name: string): string | undefined { const index = args.indexOf(name) return index >= 0 ? args[index + 1] : undefined } function emit(ws: WebSocket, payload: Record) { ws.send(JSON.stringify(payload) + '\n') } function delay(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } function extractUserText(message: any): string { const content = message?.message?.content if (!Array.isArray(content)) return '' return content .filter((block: any) => block?.type === 'text' && typeof block.text === 'string') .map((block: any) => block.text) .join(' ') } const sdkUrl = getArg('--sdk-url') const sessionId = getArg('--session-id') || crypto.randomUUID() const initMode = process.env.MOCK_SDK_INIT_MODE || 'on_open' const initDelayMs = Number(process.env.MOCK_SDK_INIT_DELAY_MS || '0') const streamDelayMs = Number(process.env.MOCK_SDK_STREAM_DELAY_MS || '0') const exitAfterOpenMs = Number(process.env.MOCK_SDK_EXIT_AFTER_OPEN_MS || '0') const exitAfterFirstUserMs = Number(process.env.MOCK_SDK_EXIT_AFTER_FIRST_USER_MS || '0') const mcpStatusDelayMs = Number(process.env.MOCK_SDK_MCP_STATUS_DELAY_MS || '0') const startupStdout = process.env.MOCK_SDK_STARTUP_STDOUT || '' const exitBeforeSdkMs = Number(process.env.MOCK_SDK_EXIT_BEFORE_SDK_MS || '0') let initSent = false let firstUserExitScheduled = false if (!sdkUrl) { console.error('Missing --sdk-url') process.exit(1) } if (startupStdout) { console.log(startupStdout) } if (exitBeforeSdkMs > 0) { setTimeout(() => process.exit(1), exitBeforeSdkMs) } const ws = new WebSocket(sdkUrl) function sendInit() { if (initSent) return initSent = true emit(ws, { type: 'system', subtype: 'init', model: 'mock-opus', slash_commands: [{ name: 'help', description: 'Show help' }], session_id: sessionId, }) } ws.addEventListener('open', () => { if (initMode !== 'on_first_user') { if (initDelayMs > 0) { setTimeout(sendInit, initDelayMs) } else { sendInit() } } if (exitAfterOpenMs > 0) { setTimeout(() => process.exit(1), exitAfterOpenMs) } }) ws.addEventListener('message', (event) => { const payload = typeof event.data === 'string' ? event.data : String(event.data) const lines = payload.split('\n').map(line => line.trim()).filter(Boolean) void (async () => { for (const line of lines) { const parsed = JSON.parse(line) if (parsed.type === 'user') { sendInit() if (exitAfterFirstUserMs > 0 && !firstUserExitScheduled) { firstUserExitScheduled = true setTimeout(() => process.exit(1), exitAfterFirstUserMs) continue } const text = extractUserText(parsed) const slashCommand = text.trim() if (slashCommand === '/cost') { emit(ws, { type: 'system', subtype: 'local_command_output', content: 'Total cost: $0.0000\nTotal duration: 0s', session_id: sessionId, }) emit(ws, { type: 'result', subtype: 'success', is_error: false, result: 'Total cost: $0.0000', usage: { input_tokens: 0, output_tokens: 0 }, session_id: sessionId, }) continue } if (slashCommand === '/context') { emit(ws, { type: 'system', subtype: 'local_command_output', content: '## Context Usage\n\n| Type | Tokens |\n| --- | ---: |\n| System prompt | 123 |', session_id: sessionId, }) emit(ws, { type: 'result', subtype: 'success', is_error: false, result: 'Context usage', usage: { input_tokens: 0, output_tokens: 0 }, session_id: sessionId, }) continue } if (text.includes('trigger api error')) { emit(ws, { type: 'assistant', error: 'invalid_request', isApiErrorMessage: true, message: { role: 'assistant', content: [{ type: 'text', text: 'Prompt is too long' }], }, session_id: sessionId, }) if (text.includes('then exit')) { setTimeout(() => process.exit(1), 10) continue } emit(ws, { type: 'result', subtype: 'success', is_error: true, result: 'Prompt is too long', usage: { input_tokens: 0, output_tokens: 0 }, session_id: sessionId, }) continue } emit(ws, { type: 'stream_event', event: { type: 'message_start' }, session_id: sessionId, }) emit(ws, { type: 'stream_event', event: { type: 'content_block_start', index: 0, content_block: { type: 'text', text: '' }, }, session_id: sessionId, }) emit(ws, { type: 'stream_event', event: { type: 'content_block_delta', index: 0, delta: { type: 'thinking_delta', thinking: 'Mock thinking...' }, }, session_id: sessionId, }) if (streamDelayMs > 0) await delay(streamDelayMs) emit(ws, { type: 'stream_event', event: { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', text: `Echo: ${text}` }, }, session_id: sessionId, }) if (streamDelayMs > 0) await delay(streamDelayMs) emit(ws, { type: 'stream_event', event: { type: 'content_block_stop', index: 0 }, session_id: sessionId, }) emit(ws, { type: 'result', subtype: 'success', is_error: false, result: `Echo: ${text}`, usage: { input_tokens: 3, output_tokens: 2 }, session_id: sessionId, }) } if (parsed.type === 'control_request' && parsed.request?.subtype === 'interrupt') { emit(ws, { type: 'result', subtype: 'success', is_error: false, result: 'Interrupted', usage: { input_tokens: 0, output_tokens: 0 }, session_id: sessionId, }) } if (parsed.type === 'control_request' && parsed.request?.subtype === 'get_session_usage') { emit(ws, { type: 'control_response', response: { subtype: 'success', request_id: parsed.request_id, response: { totalCostUSD: 0.1234, costDisplay: '$0.1234', hasUnknownModelCost: false, totalAPIDuration: 4, totalDuration: 43, totalLinesAdded: 0, totalLinesRemoved: 0, totalInputTokens: 27000, totalOutputTokens: 41, totalCacheReadInputTokens: 0, totalCacheCreationInputTokens: 0, totalWebSearchRequests: 0, models: [{ model: 'mock-opus', displayName: 'mock-opus', inputTokens: 27000, outputTokens: 41, cacheReadInputTokens: 0, cacheCreationInputTokens: 0, webSearchRequests: 0, costUSD: 0.1234, costDisplay: '$0.1234', contextWindow: 200000, maxOutputTokens: 8192, }], }, }, session_id: sessionId, }) } if (parsed.type === 'control_request' && parsed.request?.subtype === 'get_context_usage') { emit(ws, { type: 'control_response', response: { subtype: 'success', request_id: parsed.request_id, response: { categories: [ { name: 'System prompt', tokens: 6800, color: '#8a8a8a' }, { name: 'MCP tools', tokens: 5900, color: '#06b6d4' }, { name: 'Messages', tokens: 2400, color: '#7c3aed' }, { name: 'Free space', tokens: 132000, color: '#a1a1aa' }, ], totalTokens: 27000, maxTokens: 200000, rawMaxTokens: 200000, percentage: 13, gridRows: Array.from({ length: 10 }, (_, row) => Array.from({ length: 10 }, (_, col) => { const index = row * 10 + col return { color: index < 13 ? '#06b6d4' : '#a1a1aa', isFilled: index < 13, categoryName: index < 13 ? 'Used' : 'Free space', tokens: 2000, percentage: 1, squareFullness: index < 13 ? 1 : 0, } }), ), model: 'mock-opus', estimateOnly: parsed.request.estimateOnly === true, memoryFiles: [], mcpTools: [{ name: 'mock_tool', serverName: 'mock', tokens: 144, isLoaded: true }], agents: [], skills: { totalSkills: 1, includedSkills: 1, tokens: 3000, skillFrontmatter: [] }, isAutoCompactEnabled: true, apiUsage: null, }, }, session_id: sessionId, }) } if (parsed.type === 'control_request' && parsed.request?.subtype === 'mcp_status') { if (mcpStatusDelayMs > 0) { await delay(mcpStatusDelayMs) } emit(ws, { type: 'control_response', response: { subtype: 'success', request_id: parsed.request_id, response: { mcpServers: [{ name: 'mock', status: 'connected' }], }, }, session_id: sessionId, }) } } })() }) ws.addEventListener('close', () => { process.exit(0) })