Merge 519c1d3406e7c83234cf26ae76eaf5f46ba3bcfd into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
6c88d997f0
@ -67,6 +67,37 @@ describe("parsePromptEventLine", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("parses JSON-RPC result stopReason as done", () => {
|
||||
const line = JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
result: {
|
||||
stopReason: "end_turn",
|
||||
},
|
||||
});
|
||||
expect(parsePromptEventLine(line)).toEqual({
|
||||
type: "done",
|
||||
stopReason: "end_turn",
|
||||
});
|
||||
});
|
||||
|
||||
it("parses JSON-RPC errors as runtime errors", () => {
|
||||
const line = JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 2,
|
||||
error: {
|
||||
code: -32002,
|
||||
message: "Resource not found",
|
||||
},
|
||||
});
|
||||
expect(parsePromptEventLine(line)).toEqual({
|
||||
type: "error",
|
||||
message: "Resource not found",
|
||||
code: undefined,
|
||||
retryable: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps compatibility with simplified text/done lines", () => {
|
||||
expect(parsePromptEventLine(JSON.stringify({ type: "text", content: "alpha" }))).toEqual({
|
||||
type: "text_delta",
|
||||
|
||||
@ -65,6 +65,28 @@ function resolveStructuredPromptPayload(parsed: Record<string, unknown>): {
|
||||
}
|
||||
}
|
||||
|
||||
const result = parsed.result;
|
||||
if (isRecord(result)) {
|
||||
const stopReason = asTrimmedString(result.stopReason);
|
||||
if (stopReason) {
|
||||
return {
|
||||
type: "done",
|
||||
payload: result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const rpcError = parsed.error;
|
||||
if (isRecord(rpcError)) {
|
||||
const message = asTrimmedString(rpcError.message);
|
||||
if (message) {
|
||||
return {
|
||||
type: "error",
|
||||
payload: rpcError,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const sessionUpdate = asOptionalString(parsed.sessionUpdate) as AcpSessionUpdateTag | undefined;
|
||||
if (sessionUpdate) {
|
||||
return {
|
||||
|
||||
@ -91,12 +91,28 @@ function createToolEvent(params: {
|
||||
} as unknown as EventFrame;
|
||||
}
|
||||
|
||||
function createChatFinalEvent(sessionKey: string): EventFrame {
|
||||
function createChatFinalEvent(
|
||||
sessionKey: string,
|
||||
params: {
|
||||
text?: string;
|
||||
stopReason?: string;
|
||||
} = {},
|
||||
): EventFrame {
|
||||
return {
|
||||
event: "chat",
|
||||
payload: {
|
||||
sessionKey,
|
||||
state: "final",
|
||||
...(params.stopReason ? { stopReason: params.stopReason } : {}),
|
||||
...(params.text
|
||||
? {
|
||||
message: {
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: params.text }],
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
} as unknown as EventFrame;
|
||||
}
|
||||
@ -881,6 +897,39 @@ describe("acp tool streaming bridge behavior", () => {
|
||||
});
|
||||
|
||||
describe("acp session metadata and usage updates", () => {
|
||||
it("flushes final assistant text from chat.final message payload before resolving prompt", async () => {
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
const connection = createAcpConnection();
|
||||
const sessionUpdate = connection.__sessionUpdateMock;
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "chat.send") {
|
||||
return new Promise(() => {});
|
||||
}
|
||||
return { ok: true };
|
||||
}) as GatewayClient["request"];
|
||||
const agent = new AcpGatewayAgent(connection, createAcpGateway(request), {
|
||||
sessionStore,
|
||||
});
|
||||
|
||||
await agent.loadSession(createLoadSessionRequest("usage-session"));
|
||||
sessionUpdate.mockClear();
|
||||
|
||||
const promptPromise = agent.prompt(createPromptRequest("usage-session", "hello"));
|
||||
|
||||
await agent.handleGatewayEvent(createChatFinalEvent("usage-session", { text: "final answer" }));
|
||||
|
||||
await expect(promptPromise).resolves.toEqual({ stopReason: "end_turn" });
|
||||
expect(sessionUpdate).toHaveBeenCalledWith({
|
||||
sessionId: "usage-session",
|
||||
update: {
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "final answer" },
|
||||
},
|
||||
});
|
||||
|
||||
sessionStore.clearAllSessionsForTest();
|
||||
});
|
||||
|
||||
it("emits a fresh usage snapshot after prompt completion when gateway totals are available", async () => {
|
||||
const sessionStore = createInMemorySessionStore();
|
||||
const connection = createAcpConnection();
|
||||
|
||||
@ -812,6 +812,9 @@ export class AcpGatewayAgent implements Agent {
|
||||
}
|
||||
|
||||
if (state === "final") {
|
||||
if (messageData) {
|
||||
await this.handleDeltaEvent(pending.sessionId, messageData);
|
||||
}
|
||||
const rawStopReason = payload.stopReason as string | undefined;
|
||||
const stopReason: StopReason = rawStopReason === "max_tokens" ? "max_tokens" : "end_turn";
|
||||
await this.finishPrompt(pending.sessionId, pending, stopReason);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user