#!/usr/bin/env node /** * Provider smoke for local Goose v1.49: * 1. Upsert custom_tkmind_relay_deepseek via /config/custom-providers * 2. POST /agent/update_provider * 3. Optional full reply Finish when DEEPSEEK_API_KEY is set */ import fs from 'node:fs'; import path from 'node:path'; import { randomUUID } from 'node:crypto'; import { fileURLToPath } from 'node:url'; import { Readable } from 'node:stream'; import { Agent, fetch } from 'undici'; import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs'; enforceRealLlmGate('check-goosed-v149-provider.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')); const port = process.env.GOOSE_V149_PORT || '18049'; const host = process.env.GOOSE_V149_HOST || '127.0.0.1'; const secret = process.env.GOOSE_SERVER__SECRET_KEY || 'local-v149-dev-secret'; 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 apiUrl = process.env.GOOSE_V149_PROVIDER_API_URL || process.env.DEEPSEEK_API_BASE_URL || 'https://api.deepseek.com/v1'; const replyTimeoutMs = Number(process.env.GOOSE_V149_PROVIDER_REPLY_TIMEOUT_MS || 90_000); const blockedHosts = ['180.159.29.143', '120.26.184.105']; if (blockedHosts.includes(host)) { console.error(`GOOSE_V149_PROVIDER_FAIL: refusing production host ${host}`); process.exit(1); } const base = `https://${host}:${port}`; const dispatcher = new Agent({ connect: { rejectUnauthorized: false } }); async function apiFetch(pathname, init = {}) { const headers = { ...(init.headers ?? {}), 'X-Secret-Key': secret, }; if (init.body && !headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } return fetch(`${base}${pathname}`, { ...init, headers, dispatcher, }); } async function apiJson(pathname, init = {}) { const response = await apiFetch(pathname, init); const text = await response.text(); if (!response.ok) { throw new Error(`${pathname} ${response.status}: ${text.slice(0, 800)}`); } return text.trim() ? JSON.parse(text) : {}; } async function upsertCustomProvider() { const body = { engine: 'openai', display_name: 'tkmind_relay_deepseek', api_url: apiUrl, api_key: apiKey || 'placeholder-not-used-for-config-only', models: [model, 'deepseek-reasoner'], supports_streaming: true, requires_auth: Boolean(apiKey), preserves_thinking: false, }; let response = await apiFetch( `/config/custom-providers/${encodeURIComponent(providerId)}`, { method: 'PUT', body: JSON.stringify(body) }, ); if (response.ok) { console.log(`GOOSE_V149_PROVIDER_CONFIG_OK: updated ${providerId}`); return; } const putText = await response.text().catch(() => ''); const missing = response.status === 404 || /not found/i.test(putText); if (!missing) { throw new Error(`PUT custom provider failed: ${response.status} ${putText}`); } response = await apiFetch('/config/custom-providers', { method: 'POST', body: JSON.stringify(body), }); const postText = await response.text(); if (!response.ok) { throw new Error(`POST custom provider failed: ${response.status} ${postText}`); } const created = JSON.parse(postText); console.log( `GOOSE_V149_PROVIDER_CONFIG_OK: created ${created.provider_name ?? providerId}`, ); } async function waitForFinish(sessionId, requestId) { const eventsResponse = await apiFetch(`/sessions/${sessionId}/events`, { method: 'GET', headers: { Accept: 'text/event-stream' }, }); if (!eventsResponse.ok || !eventsResponse.body) { const text = await eventsResponse.text().catch(() => ''); throw new Error(`events ${eventsResponse.status}: ${text.slice(0, 400)}`); } const replyResponse = await apiFetch(`/sessions/${sessionId}/reply`, { method: 'POST', body: JSON.stringify({ request_id: requestId, user_message: { role: 'user', created: Date.now(), content: [{ type: 'text', text: 'Reply with exactly one word: pong' }], metadata: { userVisible: true, agentVisible: true, displayText: 'ping' }, }, }), }); if (!replyResponse.ok) { const text = await replyResponse.text().catch(() => ''); throw new Error(`reply ${replyResponse.status}: ${text.slice(0, 400)}`); } replyResponse.body?.cancel?.(); const reader = Readable.fromWeb(eventsResponse.body); const decoder = new TextDecoder(); let buffer = ''; const deadline = Date.now() + replyTimeoutMs; for await (const chunk of reader) { if (Date.now() > deadline) { throw new Error(`timeout waiting for Finish (${replyTimeoutMs}ms)`); } buffer += decoder.decode(chunk, { stream: true }); const frames = buffer.split('\n\n'); buffer = frames.pop() ?? ''; for (const frame of frames) { let data = ''; for (const line of frame.split('\n')) { if (line.startsWith('data:')) data += line.slice(5).trim(); } if (!data) continue; let event; try { event = JSON.parse(data); } catch { continue; } const routingId = event.chat_request_id ?? event.request_id; if (routingId && routingId !== requestId) continue; if (event.type === 'Finish') return 'finish'; if (event.type === 'Error') { throw new Error(String(event.error ?? 'provider reply error')); } } } throw new Error('SSE closed before Finish'); } try { if (!apiKey) { console.log('GOOSE_V149_PROVIDER_SKIP_REPLY: no DEEPSEEK_API_KEY — config/update_provider only'); } await upsertCustomProvider(); const session = await apiJson('/agent/start', { method: 'POST', body: JSON.stringify({ working_dir: workingDir }), }); if (!session?.id) throw new Error('missing session id'); await apiJson('/agent/update_provider', { method: 'POST', body: JSON.stringify({ session_id: session.id, provider: providerId, model, }), }); console.log(`GOOSE_V149_PROVIDER_UPDATE_OK: session=${session.id} provider=${providerId} model=${model}`); if (apiKey) { const requestId = randomUUID(); const outcome = await waitForFinish(session.id, requestId); console.log( `GOOSE_V149_PROVIDER_REPLY_OK: session=${session.id} request=${requestId} outcome=${outcome}`, ); } console.log('GOOSE_V149_PROVIDER_SMOKE_OK'); } catch (error) { console.error(`GOOSE_V149_PROVIDER_FAIL: ${error.message}`); process.exit(1); }