Files
next-tjwater-agent/tests/integration/response-input-smoke.ts
T
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context,
while retaining memory, skills, actions and publication plugins. Add nearby-model
queries and explicit chat/task/learning model defaults.

Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped
session ownership, pending input edits and investigation communication.

Disable local-provider discovery: Server /health allowed native vLLM to probe
/v1/models on port 8000. A native before/after regression prevents recurrence.

Validation: 170 focused Bun tests; service/plugin typechecks; native discovery,
provider-discovery and input-inbox smoke checks; no paid model inference.
2026-10-19 18:46:58 +08:00

90 lines
6.0 KiB
TypeScript

// Native inbox acceptance and delivery changes against a local deterministic model.
import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { startOpencode2 } from "../../src/runtime/opencode2-server.js";
import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
import { ResponseInputTracker } from "../../src/chat/response-inputs.js";
import { streamPromptResponse } from "../../src/routes/chatStream.js";
import { config } from "../../src/config.js";
const root = await mkdtemp(join(tmpdir(), "tjwater-response-input-"));
config.LLM_REQUEST_AUDIT_LOG_PATH = join(root, "fixture-audit.log");
let release = () => {};
const gate = new Promise<void>((resolve) => { release = resolve; });
let releaseSecond = () => {};
const secondGate = new Promise<void>((resolve) => { releaseSecond = resolve; });
const prompts: unknown[] = [];
const fixture = Bun.serve({ hostname: "127.0.0.1", port: 0, async fetch(request) {
if (new URL(request.url).pathname !== "/v1/chat/completions") return new Response(null, { status: 404 });
prompts.push(await request.json());
if (prompts.length === 1) await gate;
if (prompts.length === 2) await secondGate;
const chunk = (delta: object, finish_reason: string | null) => JSON.stringify({ id: "fixture", object: "chat.completion.chunk", created: 1,
model: "fake", choices: [{ index: 0, delta, finish_reason }] });
return new Response(`data: ${chunk({ role: "assistant", content: "Local completed." }, null)}\n\ndata: ${chunk({}, "stop")}\n\ndata: [DONE]\n\n`,
{ headers: { "Content-Type": "text/event-stream" } });
} });
let native: Awaited<ReturnType<typeof startOpencode2>> | undefined;
const wait = async (label: string, predicate: () => boolean | Promise<boolean>) => {
const deadline = Date.now() + 15_000;
while (!await predicate()) { if (Date.now() > deadline) throw new Error(`native input smoke timed out: ${label}, fixture requests=${prompts.length}`); await Bun.sleep(20); }
};
try {
native = await startOpencode2({ command: process.env.OPENCODE_COMMAND ?? "opencode2", hostname: "127.0.0.1", port: 0, timeout: 30_000,
config: { model: "fixture/fake", agents: { instruction: { mode: "primary", model: "fixture/fake", system: "Reply with a short local fixture answer." } },
providers: { fixture: { name: "Fixture", package: "@opencode/ai/providers/openai-compatible",
settings: { baseURL: `http://127.0.0.1:${fixture.port}/v1`, apiKey: "LOCAL_FIXTURE" },
models: { fake: { capabilities: { tools: true }, limit: { context: 32768, output: 1024 } } } } } },
env: { OPENCODE_DB: join(root, "native.db"), XDG_DATA_HOME: join(root, "data"), XDG_CONFIG_HOME: join(root, "config"),
XDG_CACHE_HOME: join(root, "cache"), XDG_STATE_HOME: join(root, "state"), TJWATER_AGENT_MCP_CONNECTION_PATH: join(root, "no-connection") },
});
const client = native.client;
const session = await client.session.create({ title: "Inbox smoke fixture", location: { directory: root } });
await client.session.switchModel({ sessionID: session.id, model: { providerID: "fixture", id: "fake" } });
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), { ensureClient: async () => client }) as OpencodeRuntimeAdapter;
const consumed: string[] = [];
const tracker = new ResponseInputTracker(runtime, session.id, (input) => { if (input.status === "delivered") consumed.push(input.id); });
const tokens: string[] = [];
let finished = false;
const running = streamPromptResponse({ runtime, sessionId: session.id, clientSessionId: session.id, message: "Original local task", model: "fixture/fake",
beforePrompt: () => tracker.restore(), onPromptDispatched: () => tracker.markReady(),
onInboxEvent: (type, data) => tracker.observe(type, data), waitForUserInput: async () => !await tracker.canClose(),
write: (event, data) => { if (event === "token") tokens.push(String(data.content)); } });
void running.catch(() => {});
void running.then(() => { finished = true; }, () => { finished = true; });
await wait("first local generation", () => prompts.length >= 1);
const item = await tracker.submit({ id: `msg_${"1".repeat(32)}`, text: "Additional local user input", delivery: "queue", status: "pending" });
assert.equal(item.delivery, "queue");
assert.equal((await client.session.inbox.list({ sessionID: session.id })).find((entry) => entry.id === item.id)?.delivery, "queue");
await client.session.inbox.update({ sessionID: session.id, inboxID: item.id, delivery: "steer" });
assert.equal((await client.session.inbox.list({ sessionID: session.id })).find((entry) => entry.id === item.id)?.delivery, "steer");
await client.session.inbox.update({ sessionID: session.id, inboxID: item.id, delivery: "queue" });
release();
await wait("queued continuation", () => prompts.length >= 2);
await Bun.sleep(100);
assert.equal(finished, false, "managed stream closed while queued generation was running");
releaseSecond();
let finishTimer: ReturnType<typeof setTimeout> | undefined;
const result = await Promise.race([running, new Promise<never>((_resolve, reject) => {
finishTimer = setTimeout(() => reject(new Error("managed stream did not finish")), 10_000);
finishTimer.unref();
})]).finally(() => clearTimeout(finishTimer));
assert.equal(result.failed, false);
assert.equal(result.aborted, false);
assert.ok(consumed.includes(item.id));
assert.ok(tokens.join("").split("Local completed.").length >= 3, "managed stream closed before queued answer");
assert.ok(JSON.stringify(prompts[1]).includes("Additional local user input"));
assert.ok(!(await client.session.inbox.list({ sessionID: session.id })).some((entry) => entry.id === item.id));
await client.session.remove({ sessionID: session.id });
console.log("Native queue delivery, pending steer/queue changes and automatic continuation passed; local fixture only.");
} finally {
release();
releaseSecond();
native?.close();
if (native) await native.exited;
fixture.stop(true);
await rm(root, { recursive: true, force: true });
}