feat(agent): support Responses steering and project-aware tools
Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running turns active while users steer or queue follow-ups and edit pending delivery. Refresh the model catalog before generation and preserve approval mode selection. Integrate scoped investigation messaging/presentation, geographic place selection, current Agent composer styles and supporting workbench contracts. Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and production build. Preserve direct Server/Agent routing and project authentication.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { responsesFixture } from "./support/responses-fixture";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { mockScadaApi } from "./support/mock-scada-api";
|
||||
|
||||
@@ -10,7 +11,7 @@ test("submits a custom answer with the actionable id after a later tool placehol
|
||||
await page.getByRole("button", { name: /展开 Agent 助手面板/ }).click();
|
||||
|
||||
const prompt = page
|
||||
.getByPlaceholder(/输入调度问题/)
|
||||
.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" })
|
||||
.filter({ visible: true })
|
||||
.first();
|
||||
await prompt.fill("测试 question 自定义回答");
|
||||
@@ -29,7 +30,7 @@ test("submits a custom answer with the actionable id after a later tool placehol
|
||||
.poll(() => replies)
|
||||
.toEqual([
|
||||
{
|
||||
pathname: "/api/v1/agent/chat/question/question-1/reply",
|
||||
pathname: "/api/v1/question/question-1/reply",
|
||||
body: {
|
||||
session_id: "question-session",
|
||||
answers: [["分析高峰时段压力"]]
|
||||
@@ -48,7 +49,7 @@ test("keeps the custom answer control within a 375px Agent sheet", async ({ page
|
||||
await page.getByRole("button", { name: /Agent 工作台抽屉高度/ }).click();
|
||||
|
||||
const panel = page.locator('aside[aria-label="Agent 命令面板"]').filter({ visible: true });
|
||||
const prompt = panel.getByPlaceholder(/输入调度问题/);
|
||||
const prompt = panel.getByRole("textbox", { name: "询问供水问题,或描述需要处理的任务" });
|
||||
await prompt.fill("测试窄屏 question");
|
||||
await panel.getByRole("button", { name: "发送 Agent 指令" }).click();
|
||||
await panel.getByRole("radio", { name: "自定义回答" }).click();
|
||||
@@ -69,7 +70,7 @@ async function mockQuestionApi(page: Page, replies: Array<{ pathname: string; bo
|
||||
body: `globalThis.__TJWATER_CONFIG__ = ${JSON.stringify({
|
||||
TJWATER_AUTH_MODE: "disabled",
|
||||
TJWATER_SERVER_API_BASE_URL: "https://tjwater-api.invalid",
|
||||
TJWATER_AGENT_API_BASE_URL: "http://127.0.0.1:8787",
|
||||
TJWATER_AGENT_URL: "http://127.0.0.1:8787",
|
||||
TJWATER_MAP_URL: "https://question-map.invalid/geoserver",
|
||||
TJWATER_GEOSERVER_WORKSPACE: "tjwater_next",
|
||||
TJWATER_MAPBOX_ACCESS_TOKEN: "",
|
||||
@@ -78,17 +79,23 @@ async function mockQuestionApi(page: Page, replies: Array<{ pathname: string; bo
|
||||
})
|
||||
);
|
||||
await page.route("https://question-map.invalid/**", (route) => route.fulfill({ status: 204 }));
|
||||
await page.route("**/api/v1/agent/chat/**", async (route) => {
|
||||
await page.route("http://127.0.0.1:8787/api/v1/**", async (route) => {
|
||||
const request = route.request();
|
||||
const pathname = new URL(request.url()).pathname;
|
||||
if (request.method() === "OPTIONS") {
|
||||
await route.fulfill({ status: 204, headers: {
|
||||
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Methods": "GET, POST, OPTIONS"
|
||||
} });
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathname.endsWith("/stream")) {
|
||||
if (pathname.endsWith("/responses")) {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Cache-Control": "no-cache",
|
||||
"Content-Type": "text/event-stream",
|
||||
"X-Vercel-AI-UI-Message-Stream": "v1"
|
||||
"Content-Type": "text/event-stream"
|
||||
},
|
||||
body: createQuestionStream()
|
||||
});
|
||||
@@ -176,5 +183,5 @@ function createQuestionStream() {
|
||||
{ type: "finish", finishReason: "stop" }
|
||||
];
|
||||
|
||||
return parts.map((part) => `data: ${JSON.stringify(part)}\n\n`).join("");
|
||||
return responsesFixture(parts);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user