#!/usr/bin/env node /** * Phase 2: DeepSeek provider completes a tool round (todo_write) on v1.49. * Requires DEEPSEEK_API_KEY (or GOOSE_V149_TEST_API_KEY). */ import fs from 'node:fs'; import path from 'node:path'; import { randomUUID } from 'node:crypto'; import { fileURLToPath } from 'node:url'; import { collectReplyEvents, createV149Client } from './goose-v149-sse.mjs'; import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs'; enforceRealLlmGate('check-goosed-v149-deepseek-tools.mjs'); const memindRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); function loadEnvFile(filePath) { if (!fs.existsSync(filePath)) return; for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; const eq = trimmed.indexOf('='); if (eq < 0) continue; const key = trimmed.slice(0, eq).trim(); let value = trimmed.slice(eq + 1).trim(); if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } if (!process.env[key]) process.env[key] = value; } } loadEnvFile(path.join(memindRoot, '.env')); loadEnvFile(path.join(memindRoot, '.env.local')); const client = createV149Client(); const workingDir = process.env.GOOSE_V149_WORKING_DIR || memindRoot; const providerId = process.env.GOOSE_V149_PROVIDER_ID || 'custom_tkmind_relay_deepseek'; const model = process.env.GOOSE_V149_PROVIDER_MODEL || 'deepseek-chat'; const apiKey = process.env.DEEPSEEK_API_KEY || process.env.GOOSE_V149_TEST_API_KEY || ''; const timeoutMs = Number(process.env.GOOSE_V149_DEEPSEEK_TOOLS_TIMEOUT_MS || 120_000); function contentBlocks(event) { if (event?.type === 'Message') return event.message?.content ?? []; if (event?.type === 'UpdateConversation') { const conversation = event.conversation ?? []; return conversation.flatMap((message) => message?.content ?? []); } return []; } function blockLooksLikeTool(block = {}) { const type = String(block.type ?? block.content_type ?? '').toLowerCase(); if (type === 'toolrequest' || type === 'toolresponse') return true; if (type.includes('tool')) return true; if (block.tool_call || block.toolCall || block.tool_request || block.toolRequest) return true; if (block.name && (block.arguments || block.input)) return true; return false; } function resolveToolName(block = {}) { const direct = block.tool_call?.name ?? block.toolCall?.name ?? block.name ?? block.tool_request?.tool_call?.name ?? block.toolRequest?.tool_call?.name; if (direct) return String(direct); const nested = block.toolCall?.value?.name ?? block.tool_call?.value?.name ?? block.toolRequest?.tool_call?.value?.name ?? block.tool_request?.tool_call?.value?.name; return nested ? String(nested) : null; } function toolNamesFromEvents(events = []) { const names = new Set(); for (const event of events) { for (const block of contentBlocks(event)) { if (!blockLooksLikeTool(block)) continue; const name = resolveToolName(block); if (name) names.add(name); } } return [...names]; } async function upsertProvider() { const body = { engine: 'openai', display_name: 'tkmind_relay_deepseek', api_url: process.env.GOOSE_V149_PROVIDER_API_URL || process.env.DEEPSEEK_API_BASE_URL || 'https://api.deepseek.com/v1', api_key: apiKey, models: [model, 'deepseek-reasoner'], supports_streaming: true, requires_auth: true, preserves_thinking: false, }; let response = await client.apiFetch( `/config/custom-providers/${encodeURIComponent(providerId)}`, { method: 'PUT', body: JSON.stringify(body) }, ); if (!response.ok && (response.status === 404 || /not found/i.test(await response.text()))) { await client.apiJson('/config/custom-providers', body); } } async function main() { if (!apiKey) { console.log('GOOSE_V149_DEEPSEEK_TOOLS_SKIP: no DEEPSEEK_API_KEY'); process.exit(0); } await upsertProvider(); const session = await client.apiJson('/agent/start', { working_dir: workingDir, extension_overrides: [ { type: 'platform', name: 'todo', description: 'todo list for tool-round smoke', display_name: 'Todo', bundled: false, available_tools: ['todo_write'], }, { type: 'platform', name: 'skills', description: 'skills', available_tools: [], }, ], }); if (!session?.id) throw new Error('missing session id'); await client.apiJson('/agent/update_provider', { session_id: session.id, provider: providerId, model, }); const requestId = randomUUID(); const result = await collectReplyEvents({ client, sessionId: session.id, requestId, timeoutMs, userMessage: { role: 'user', created: Date.now(), content: [{ type: 'text', text: 'You must call the todo_write tool exactly once with content "- [ ] goose v149 deepseek tool smoke". ' + 'After the tool succeeds, reply with exactly one word: done', }], metadata: { userVisible: true, agentVisible: true, displayText: 'deepseek tool smoke', }, }, }); if (result.outcome !== 'finish') { throw new Error( `expected Finish, got ${result.outcome}: ${result.errorEvent?.error ?? 'timeout'}`, ); } const toolNames = toolNamesFromEvents(result.events); const hasTodoTool = toolNames.some((name) => /todo_write/i.test(name)); if (!hasTodoTool) { throw new Error( `no todo_write tool activity in SSE events; tools=${toolNames.join(',') || 'none'}`, ); } const outputDir = path.join(memindRoot, 'docs', 'baselines'); fs.mkdirSync(outputDir, { recursive: true }); const evidencePath = path.join( outputDir, `goose-v149-deepseek-tools-evidence-${new Date().toISOString().replace(/[:.]/g, '-')}.json`, ); fs.writeFileSync( evidencePath, `${JSON.stringify({ schemaVersion: 'goose-v149-deepseek-tools-v1', capturedAt: new Date().toISOString(), sessionId: session.id, requestId, providerId, model, toolNames, finishReason: result.finishEvent?.reason ?? null, eventTypes: result.events.map((event) => event.type), }, null, 2)}\n`, 'utf8', ); console.log(`GOOSE_V149_DEEPSEEK_TOOLS_OK: session=${session.id} tools=${toolNames.join(',')}`); console.log(` evidence=${evidencePath}`); } main().catch((error) => { console.error(`GOOSE_V149_DEEPSEEK_TOOLS_FAIL: ${error.message}`); process.exit(1); });