Files
agent/scripts/verify-editor-api.mjs

101 lines
5.4 KiB
JavaScript

import { createInterface } from "node:readline";
import assert from "node:assert/strict";
// Test the web proxy/API and real model without sending licensed CMS content to it.
// Supply credentials only over hidden stdin. Never log response bodies or secrets.
const base = "http://127.0.0.1:5173";
const reader = createInterface({ input: process.stdin });
let sessionId;
let stage = "input";
const abort = new AbortController();
async function request(path, method = "GET", body) {
const response = await fetch(base + path, {
method, redirect: "error", signal: AbortSignal.timeout(30_000),
headers: { Origin: base, ...(body === undefined ? {} : { "Content-Type": "application/json" }) },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
const result = await response.json();
if (!response.ok) {
const error = new Error("API request failed");
error.code = /^[A-Z_]{1,80}$/.test(result.error?.code ?? "") ? result.error.code : "API_REQUEST_FAILED";
throw error;
}
return result;
}
console.log("Provide CMS email/password as one JSON line on hidden stdin.");
try {
const input = JSON.parse((await reader[Symbol.asyncIterator]().next()).value);
reader.close();
stage = "discovery";
const catalog = await request("/api/skills");
assert(catalog.skills.some((skill) => skill.name === "swads-short-drama-editor"));
({ sessionId } = await request("/api/sessions", "POST"));
stage = "login";
let login;
try { login = await request(`/api/sessions/${sessionId}/cms/connection`, "POST", { email: input.email, password: input.password }); }
finally { input.email = ""; input.password = ""; }
assert.equal(login.connection.connected, true);
stage = "catalog";
const dramas = await request(`/api/sessions/${sessionId}/cms/dramas`);
assert(dramas.dramas.length > 0);
console.log(JSON.stringify({ stage, connected: true, dramaCount: dramas.dramas.length, skillDiscovered: true }));
stage = "model";
const timeout = setTimeout(() => abort.abort(), 150_000);
const stream = await fetch(`${base}/api/sessions/${sessionId}/events`, { signal: abort.signal });
const streamReader = stream.body.getReader();
const sent = await request(`/api/sessions/${sessionId}/messages`, "POST", {
text: "做 swads-short-drama-editor 接入验收:先完整读取该 Skill 的 SKILL.md 和 references/cms-source-contract.md,然后调用 cms_connection_status。不要读取 CMS 剧集、字幕、任何业务数据,不要调用其他工具或生成文件。最后用中文简短说明已经连接和仍然缺失的能力,不要声称成片功能已经完成。",
attachmentIds: [],
});
let pending = "";
let terminal = false;
let skillLoaded = false;
let referenceRead = false;
const referenceCalls = new Set();
let statusCompleted = false;
let answerReceived = false;
const decoder = new TextDecoder();
try {
while (!terminal) {
const { value, done } = await streamReader.read();
assert(!done, "Unexpected stream end");
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;
assert.notEqual(event.type, "approval.required", "Unexpected content access request");
if (event.type === "skill.loaded") skillLoaded = true;
if (event.type === "tool.completed" && event.payload.toolName === "cms_connection_status") {
const result = event.payload.result?.details ?? event.payload.result;
statusCompleted = result?.connected === true && result?.capabilities?.rendering === false;
}
if (event.type === "tool.requested" && event.payload.toolName === "read" && JSON.stringify(event.payload.arguments).includes("references/cms-source-contract.md")) referenceCalls.add(event.payload.toolCallId);
if (event.type === "tool.completed" && referenceCalls.has(event.payload.toolCallId)) referenceRead = true;
if (event.type === "assistant.delta" && event.payload.delta?.trim()) answerReceived = true;
if (["run.completed", "run.failed", "run.cancelled"].includes(event.type)) {
console.log(JSON.stringify({ terminal: event.type, skillLoaded, referenceRead, statusCompleted, answerReceived }));
assert.equal(event.type, "run.completed");
terminal = true;
}
}
}
assert(skillLoaded && referenceRead && statusCompleted && answerReceived, "Required Skill/tool events missing");
console.log(JSON.stringify({ result: "PASS", skillLoaded, referenceRead, statusCompleted, answerReceived, cmsContentSentToModel: false, videoPipelineVerified: false }));
} finally { clearTimeout(timeout); abort.abort(); }
} catch (error) {
console.error(JSON.stringify({ result: "FAIL", stage, code: error.code ?? "ASSERTION_OR_NETWORK_FAILURE" }));
process.exitCode = 1;
} finally {
reader.close(); abort.abort();
if (sessionId) {
await request(`/api/sessions/${sessionId}/cancel`, "POST").catch(() => {});
await request(`/api/sessions/${sessionId}/cms/connection`, "DELETE").catch(() => {});
await request(`/api/sessions/${sessionId}`, "DELETE").catch(() => {});
}
}