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.
This commit is contained in:
@@ -4,12 +4,9 @@ import { z } from "zod";
|
||||
import { getLocalAgentContext } from "../context/localContext.js";
|
||||
import { type ChatSessionBridge } from "../chat/sessionBridge.js";
|
||||
import { logger } from "../logger.js";
|
||||
import { type ResultReferenceResolver } from "../results/resolver.js";
|
||||
import { RESULT_REFERENCE_KIND } from "../results/store.js";
|
||||
import { type SessionMetadataStore } from "../sessions/metadataStore.js";
|
||||
import { type SessionUiStateStore } from "../sessions/uiStateStore.js";
|
||||
import { type FrontendActionCoordinator } from "../frontendAction/coordinator.js";
|
||||
import { toActorKey, toProjectKey } from "../utils/fileStore.js";
|
||||
import {
|
||||
type ActiveRun,
|
||||
type RunStatus,
|
||||
@@ -25,15 +22,16 @@ const abortPayloadSchema = z.object({
|
||||
type RegisterAuxiliaryRoutesOptions = {
|
||||
activeRuns: Map<string, ActiveRun>;
|
||||
lastRunStatuses: Map<string, RunStatus>;
|
||||
resultReferenceResolver: ResultReferenceResolver;
|
||||
sessionBridge: ChatSessionBridge;
|
||||
sessionMetadataStore: SessionMetadataStore;
|
||||
sessionUiStateStore: SessionUiStateStore;
|
||||
frontendActionCoordinator: FrontendActionCoordinator;
|
||||
};
|
||||
|
||||
const toSessionUiStateContext = (sessionId: string) => ({
|
||||
sessionId,
|
||||
const toSessionUiStateContext = (record: { sessionId: string; actorKey: string; projectKey: string }) => ({
|
||||
sessionId: record.sessionId,
|
||||
actorKey: record.actorKey,
|
||||
projectKey: record.projectKey,
|
||||
});
|
||||
|
||||
export const registerChatAuxiliaryRoutes = (
|
||||
@@ -41,38 +39,12 @@ export const registerChatAuxiliaryRoutes = (
|
||||
{
|
||||
activeRuns,
|
||||
lastRunStatuses,
|
||||
resultReferenceResolver,
|
||||
sessionBridge,
|
||||
sessionMetadataStore,
|
||||
sessionUiStateStore,
|
||||
frontendActionCoordinator,
|
||||
}: RegisterAuxiliaryRoutesOptions,
|
||||
) => {
|
||||
chatRouter.get("/result-ref/:result_ref", async (req, res) => {
|
||||
const resultRef = req.params.result_ref?.trim();
|
||||
const context = getLocalAgentContext(req);
|
||||
const clientSessionId = typeof req.query.session_id === "string" ? req.query.session_id.trim() : undefined;
|
||||
const offset = Math.max(0, Number.parseInt(String(req.query.offset ?? "0"), 10) || 0);
|
||||
const limit = Math.min(500, Math.max(1, Number.parseInt(String(req.query.limit ?? "100"), 10) || 100));
|
||||
if (!resultRef || !clientSessionId) {
|
||||
res.status(400).json({ message: "result_ref and session_id are required" });
|
||||
return;
|
||||
}
|
||||
const result = await resultReferenceResolver.getFullAuthorized(resultRef, {
|
||||
actorKey: context.actorKey, clientSessionId, projectId: context.projectId,
|
||||
}, { expectedKind: RESULT_REFERENCE_KIND.serverApiPayload });
|
||||
if (!result) {
|
||||
res.status(404).json({ message: "result_ref not found" });
|
||||
return;
|
||||
}
|
||||
const rows = Array.isArray(result.data)
|
||||
? result.data
|
||||
: result.data && typeof result.data === "object" && Array.isArray((result.data as { data?: unknown }).data)
|
||||
? (result.data as { data: unknown[] }).data
|
||||
: [result.data];
|
||||
res.json({ ...result, data: rows.slice(offset, offset + limit), page: { offset, limit, total: rows.length, has_more: offset + limit < rows.length } });
|
||||
});
|
||||
|
||||
chatRouter.post("/abort", async (req, res) => {
|
||||
const parsed = abortPayloadSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
@@ -98,6 +70,7 @@ export const registerChatAuxiliaryRoutes = (
|
||||
return;
|
||||
}
|
||||
frontendActionCoordinator.cancelSession(sessionRecord.sessionId);
|
||||
activeRuns.get(sessionRecord.sessionId)?.inputs?.close();
|
||||
const binding = await sessionBridge.abort({
|
||||
clientSessionId: sessionRecord.sessionId,
|
||||
sessionId: sessionRecord.sessionId,
|
||||
@@ -118,9 +91,9 @@ export const registerChatAuxiliaryRoutes = (
|
||||
todos: cancelBackendTodos(message.todos),
|
||||
}));
|
||||
const currentState = await sessionUiStateStore.read(
|
||||
toSessionUiStateContext(sessionRecord.sessionId),
|
||||
toSessionUiStateContext(sessionRecord),
|
||||
);
|
||||
await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord.sessionId), {
|
||||
await sessionUiStateStore.write(toSessionUiStateContext(sessionRecord), {
|
||||
sessionId: sessionRecord.sessionId,
|
||||
isTitleManuallyEdited: currentState?.isTitleManuallyEdited ?? false,
|
||||
messages: run.messages,
|
||||
@@ -130,9 +103,9 @@ export const registerChatAuxiliaryRoutes = (
|
||||
session_id: parsed.data.session_id,
|
||||
message: "请求已中断",
|
||||
});
|
||||
subscriber.close();
|
||||
}
|
||||
run.subscribers.clear();
|
||||
// The generation route emits its terminal Response after cleanup and
|
||||
// closes subscribers. Closing here would truncate the Responses stream.
|
||||
}
|
||||
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user