import { createInterface } from "node:readline"; import { readFile } from "node:fs/promises"; import { fileURLToPath } from "node:url"; const base = "http://127.0.0.1:3001"; const input = createInterface({ input: process.stdin }); console.log("Awaiting in-memory credential (no file persistence)"); const key = (await input[Symbol.asyncIterator]().next()).value?.trim(); input.close(); if (!key) throw new Error("Credential required on stdin"); const api = process.argv[2] ?? "openai-responses"; const existing = process.argv[3]; const safe = (value) => JSON.stringify(value).replaceAll(key, "[REDACTED]"); async function request(path, body, method = "POST") { const response = await fetch(base + path, { method, ...(body === undefined ? {} : { headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }), signal: AbortSignal.timeout(30_000), }); const result = await response.json(); if (!response.ok) throw new Error(safe({ status: response.status, result })); return result; } const { sessionId } = existing ? { sessionId: existing } : await request("/api/sessions"); console.log(safe({ sessionId, api })); await request(`/api/sessions/${sessionId}/model-config`, { providerId: "compatible", api, baseUrl: "https://gateway.pencil-stub.com/v1", apiKey: key, modelId: "Qwen3.8-27B-FP8", supportsImages: false, maxTokens: 4096, }, "PUT"); console.log("Model configured in Session memory"); async function run(text, approve = false) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 150_000); const response = await fetch(`${base}/api/sessions/${sessionId}/events`, { signal: controller.signal }); const reader = response.body.getReader(); const sent = await request(`/api/sessions/${sessionId}/messages`, { text, attachmentIds: [] }); const events = []; let pending = ""; let answer = ""; const decoder = new TextDecoder(); try { while (true) { const { done, value } = await reader.read(); if (done) break; pending += decoder.decode(value, { stream: true }); let boundary; while ((boundary = pending.indexOf("\n\n")) !== -1) { const frame = pending.slice(0, boundary); pending = pending.slice(boundary + 2); const data = frame.split("\n").find((line) => line.startsWith("data: "))?.slice(6); if (!data) continue; const event = JSON.parse(data); if (event.runId !== sent.runId) continue; events.push(event); if (event.type === "assistant.delta") answer += event.payload.delta; else console.log(safe({ type: event.type, payload: event.type === "tool.completed" && event.payload.toolName === "read" ? { toolName: "read", toolCallId: event.payload.toolCallId } : event.payload })); if (event.type === "approval.required") { if (!approve || event.payload.toolName !== "save_report_draft") throw new Error("Unexpected approval request"); if (process.env.LIVE_APPROVAL_MODE !== "manual") await request(`/api/approvals/${event.payload.approvalId}/approve`); } if (["run.completed", "run.failed", "run.cancelled"].includes(event.type)) { console.log(safe({ runId: sent.runId, answer, deltaCount: events.filter((e) => e.type === "assistant.delta").length })); if (event.type !== "run.completed" || !answer.trim()) throw new Error("Run did not produce a successful visible answer"); return events; } } } throw new Error("Event stream ended without a terminal run event"); } finally { clearTimeout(timer); controller.abort(); } } try { await run("请只回复:连接成功。不要调用工具。"); const events = await run("请分析 demo-account 最近 7 天广告数据,并调用 save_report_draft 保存中文报告。先读取 ads-analysis 和 report-writer 两个 Skill,再调用 mock_ads_metrics。报告正文严格控制在 400 字内,保留五个章节,每章 1 至 2 句,不要表格;数字引用工具结果,注明模拟数据。写入由本次测试批准,成功后只回复保存成功及路径。", true); for (const name of ["mock_ads_metrics", "save_report_draft"]) { if (!events.some((e) => e.type === "tool.completed" && e.payload.toolName === name)) throw new Error(`Tool did not complete: ${name}`); } if (!events.some((e) => e.type === "approval.resolved" && e.payload.status === "approved")) throw new Error("Approval was not observed"); if (events.filter((e) => e.type === "skill.loaded").length < 2) throw new Error("Both Skills must be loaded successfully"); const report = await readFile(fileURLToPath(new URL(`../.agent-studio-sessions/${sessionId}/report.md`, import.meta.url)), "utf8"); console.log(safe({ result: "PASS", sessionId, reportCharacters: report.length, skillLoaded: events.filter((e) => e.type === "skill.loaded").length })); } catch (error) { console.error(safe({ result: "FAIL", message: error.message, sessionId })); process.exitCode = 1; }