#!/usr/bin/env node /** * Multi-turn provider smoke: update_provider then two consecutive /reply rounds. */ import { randomUUID } from 'node:crypto'; import { Readable } from 'node:stream'; import { Agent, fetch } from 'undici'; import { enforceRealLlmGate } from './goose-v149-real-llm-gate.mjs'; enforceRealLlmGate('check-goosed-v149-multiturn-provider.mjs'); 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 || process.cwd(); // Align with portal-resume default (300s); 90s was too tight under queued agent-run load. const timeoutMs = Number(process.env.GOOSE_V149_MULTITURN_TIMEOUT_MS || 300_000); const provider = process.env.GOOSE_V149_TEST_PROVIDER || 'custom_qwen3-flash'; const model = process.env.GOOSE_V149_TEST_MODEL || 'qwen3.8-flash'; const restartBetweenTurns = process.env.GOOSE_V149_MULTITURN_RESTART === '1'; const base = `https://${host}:${port}`; const dispatcher = new Agent({ connect: { rejectUnauthorized: false } }); async function apiJson(pathname, body) { const response = await fetch(`${base}${pathname}`, { method: 'POST', headers: { 'X-Secret-Key': secret, 'Content-Type': 'application/json', }, body: JSON.stringify(body), dispatcher, }); const text = await response.text(); if (!response.ok) { throw new Error(`${pathname} ${response.status}: ${text.slice(0, 400)}`); } return text.trim() ? JSON.parse(text) : {}; } async function waitForTerminal(sessionId, requestId) { const eventsResponse = await fetch(`${base}/sessions/${sessionId}/events`, { headers: { Accept: 'text/event-stream', 'X-Secret-Key': secret }, dispatcher, }); if (!eventsResponse.ok || !eventsResponse.body) { throw new Error(`events ${eventsResponse.status}`); } const replyResponse = await fetch(`${base}/sessions/${sessionId}/reply`, { method: 'POST', headers: { 'X-Secret-Key': secret, 'Content-Type': 'application/json', }, body: JSON.stringify({ request_id: requestId, user_message: { role: 'user', created: Date.now(), content: [{ type: 'text', text: `multiturn ping ${requestId.slice(0, 8)}` }], metadata: { userVisible: true, agentVisible: true, displayText: 'multiturn ping' }, }, }), dispatcher, }); if (!replyResponse.ok) { const text = await replyResponse.text().catch(() => ''); throw new Error(`reply ${replyResponse.status}: ${text.slice(0, 300)}`); } replyResponse.body?.cancel?.(); const reader = Readable.fromWeb(eventsResponse.body); const decoder = new TextDecoder(); let buffer = ''; const deadline = Date.now() + timeoutMs; for await (const chunk of reader) { if (Date.now() > deadline) break; 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') { return `error:${String(event.error ?? '').slice(0, 200)}`; } } } return 'timeout'; } async function main() { const session = await apiJson('/agent/start', { working_dir: workingDir }); if (!session?.id) throw new Error('missing session id'); await apiJson('/agent/update_provider', { session_id: session.id, provider, model, }); const outcomes = []; for (let turn = 1; turn <= 2; turn += 1) { const requestId = randomUUID(); const outcome = await waitForTerminal(session.id, requestId); outcomes.push({ turn, requestId, outcome }); console.log(`GOOSE_V149_MULTITURN_EVENT: turn=${turn} outcome=${outcome}`); if (outcome !== 'finish') { throw new Error(`turn ${turn} failed: ${outcome}`); } if (restartBetweenTurns && turn === 1) { await apiJson('/agent/restart', { session_id: session.id }); console.log('GOOSE_V149_MULTITURN_EVENT: restart=ok'); } } console.log( `GOOSE_V149_MULTITURN_OK: session=${session.id} provider=${provider}/${model} ` + `turns=${outcomes.length}`, ); } main().catch((error) => { console.error(`GOOSE_V149_MULTITURN_FAIL: ${error.message}`); process.exit(1); });