| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- import { describe, expect, test } from "bun:test"
- import { makeACPFixture, makeSession, secondModel, type FixtureContext, type FixtureRequest } from "./service-fixture"
- describe("acp service prompt routing and usage", () => {
- test("routes slash commands, skills, and compact through their session endpoints", async () => {
- await using fixture = makeACPFixture({
- fetch(request, context) {
- if (request.method === "POST" && request.path === "/api/session") {
- return Response.json({ data: makeSession("ses_routes") })
- }
- if (request.method === "GET" && request.path === "/api/session/ses_routes") {
- return Response.json({ data: makeSession("ses_routes") })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_routes/command") {
- const id = requestID(request)
- completeTurn(context, "ses_routes", {
- id: `evt_${id}`,
- type: "session.input.promoted",
- data: { sessionID: "ses_routes", inputID: id },
- })
- return Response.json({ data: {} })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_routes/skill") {
- const id = requestID(request)
- completeTurn(context, "ses_routes", {
- id: id.replace(/^msg_/, "evt_"),
- type: "session.skill.activated",
- data: { sessionID: "ses_routes", skill: "verify" },
- })
- return new Response(null, { status: 204 })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_routes/compact") {
- const id = requestID(request)
- completeTurn(context, "ses_routes", {
- id: `evt_${id}`,
- type: "session.compaction.admitted",
- data: { sessionID: "ses_routes", inputID: id },
- })
- return Response.json({ data: {} })
- }
- return undefined
- },
- })
- const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })
- const commandResult = await fixture.service.prompt({
- sessionId: session.sessionId,
- prompt: [{ type: "text", text: "/review now" }],
- })
- const skillResult = await fixture.service.prompt({
- sessionId: session.sessionId,
- prompt: [{ type: "text", text: "/verify" }],
- })
- const compactResult = await fixture.service.prompt({
- sessionId: session.sessionId,
- prompt: [{ type: "text", text: "/compact" }],
- })
- expect([commandResult.stopReason, skillResult.stopReason, compactResult.stopReason]).toEqual([
- "end_turn",
- "end_turn",
- "end_turn",
- ])
- const command = fixture.requests.find((request) => request.path === "/api/session/ses_routes/command")
- const skill = fixture.requests.find((request) => request.path === "/api/session/ses_routes/skill")
- const compact = fixture.requests.find((request) => request.path === "/api/session/ses_routes/compact")
- expect(command?.body).toMatchObject({
- id: expect.any(String),
- command: "review",
- arguments: "now",
- files: [],
- delivery: "steer",
- })
- expect(skill?.body).toMatchObject({ id: expect.any(String), skill: "verify" })
- expect(compact?.body).toMatchObject({ id: expect.any(String) })
- expect(fixture.requests.some((request) => request.path === "/api/session/ses_routes/prompt")).toBe(false)
- })
- test("returns turn usage and publishes current context usage with cumulative session cost", async () => {
- const assistantTokens = {
- input: 100,
- output: 40,
- reasoning: 7,
- cache: { read: 11, write: 13 },
- }
- await using fixture = makeACPFixture({
- fetch(request, context) {
- if (request.method === "POST" && request.path === "/api/session") {
- return Response.json({ data: makeSession("ses_usage") })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_usage/model") {
- return new Response(null, { status: 204 })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_usage/prompt") {
- const id = requestID(request)
- context.send({
- id: `evt_${id}`,
- type: "session.input.promoted",
- data: { sessionID: "ses_usage", inputID: id },
- })
- context.send({
- id: "evt_step",
- type: "session.step.ended",
- data: {
- sessionID: "ses_usage",
- assistantMessageID: "msg_assistant",
- finish: "stop",
- cost: 0.5,
- tokens: assistantTokens,
- },
- })
- context.send({
- id: "evt_done",
- type: "session.execution.succeeded",
- data: { sessionID: "ses_usage" },
- })
- return Response.json({ data: {} })
- }
- if (request.method === "GET" && request.path === "/api/session/ses_usage/message/msg_assistant") {
- return Response.json({
- data: {
- id: "msg_assistant",
- type: "assistant",
- agent: "build",
- model: { providerID: "test", id: secondModel.id },
- content: [{ type: "text", text: "done" }],
- finish: "stop",
- tokens: assistantTokens,
- time: { created: 1, completed: 2 },
- },
- })
- }
- if (request.method === "GET" && request.path === "/api/session/ses_usage") {
- return Response.json({
- data: makeSession("ses_usage", {
- model: { providerID: "test", id: secondModel.id },
- cost: 3.5,
- tokens: { input: 120, output: 50, reasoning: 8, cache: { read: 30, write: 4 } },
- }),
- })
- }
- return undefined
- },
- })
- const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })
- await fixture.service.setSessionConfigOption({
- sessionId: session.sessionId,
- configId: "model",
- value: "test/second-model",
- })
- const response = await fixture.service.prompt({
- sessionId: session.sessionId,
- prompt: [{ type: "text", text: "hello" }],
- })
- expect(response).toEqual({
- stopReason: "end_turn",
- usage: {
- inputTokens: 100,
- outputTokens: 40,
- thoughtTokens: 7,
- cachedReadTokens: 11,
- cachedWriteTokens: 13,
- totalTokens: 171,
- },
- _meta: {},
- })
- expect(fixture.updates.filter((item) => item.update.sessionUpdate === "usage_update")).toEqual([
- {
- sessionId: "ses_usage",
- update: {
- sessionUpdate: "usage_update",
- used: 171,
- size: 200_000,
- cost: { amount: 3.5, currency: "USD" },
- },
- },
- ])
- })
- test("does not fail a completed prompt when the usage refresh fails", async () => {
- await using fixture = makeACPFixture({
- fetch(request, context) {
- if (request.method === "POST" && request.path === "/api/session") {
- return Response.json({ data: makeSession("ses_usage_failure") })
- }
- if (request.method === "POST" && request.path === "/api/session/ses_usage_failure/prompt") {
- const id = requestID(request)
- context.send({
- id: `evt_${id}`,
- type: "session.input.promoted",
- data: { sessionID: "ses_usage_failure", inputID: id },
- })
- context.send({
- id: "evt_step_failure",
- type: "session.step.ended",
- data: {
- sessionID: "ses_usage_failure",
- assistantMessageID: "msg_usage_failure",
- finish: "stop",
- cost: 0,
- tokens: { input: 3, output: 2, reasoning: 0, cache: { read: 0, write: 0 } },
- },
- })
- context.send({
- id: "evt_done_failure",
- type: "session.execution.succeeded",
- data: { sessionID: "ses_usage_failure" },
- })
- return Response.json({ data: {} })
- }
- if (request.method === "GET" && request.path === "/api/session/ses_usage_failure/message/msg_usage_failure") {
- return Response.json({
- data: {
- id: "msg_usage_failure",
- type: "assistant",
- agent: "build",
- model: { providerID: "test", id: "test-model" },
- content: [],
- finish: "stop",
- tokens: { input: 3, output: 2, reasoning: 0, cache: { read: 0, write: 0 } },
- time: { created: 1, completed: 2 },
- },
- })
- }
- if (request.method === "GET" && request.path === "/api/session/ses_usage_failure") {
- return new Response(null, { status: 500 })
- }
- return undefined
- },
- })
- const session = await fixture.service.newSession({ cwd: "/workspace", mcpServers: [] })
- const response = await fixture.service.prompt({
- sessionId: session.sessionId,
- prompt: [{ type: "text", text: "hello" }],
- })
- expect(response.stopReason).toBe("end_turn")
- expect(fixture.updates.some((item) => item.update.sessionUpdate === "usage_update")).toBe(false)
- })
- })
- function requestID(request: FixtureRequest) {
- if (!request.body || typeof request.body !== "object") throw new Error(`missing body for ${request.path}`)
- const id = Reflect.get(request.body, "id")
- if (typeof id !== "string") throw new Error(`missing prompt id for ${request.path}`)
- return id
- }
- function completeTurn(context: FixtureContext, sessionID: string, start: unknown) {
- context.send(start)
- context.send({
- id: `evt_done_${sessionID}`,
- type: "session.execution.succeeded",
- data: { sessionID },
- })
- }
|