|
| 1 | +import type { ContentBlock } from "@agentclientprotocol/sdk"; |
| 2 | +import type { AcpMessage } from "@shared/types/session-events"; |
1 | 3 | import { describe, expect, it } from "vitest"; |
2 | 4 |
|
3 | | -import { isFatalSessionError } from "./session"; |
| 5 | +import { makeAttachmentUri } from "./promptContent"; |
| 6 | +import { extractUserPromptsFromEvents, isFatalSessionError } from "./session"; |
4 | 7 |
|
5 | 8 | describe("isFatalSessionError", () => { |
6 | 9 | it("detects fatal 'Internal error' pattern", () => { |
@@ -37,3 +40,130 @@ describe("isFatalSessionError", () => { |
37 | 40 | expect(isFatalSessionError("")).toBe(false); |
38 | 41 | }); |
39 | 42 | }); |
| 43 | + |
| 44 | +function promptEvent(prompt: ContentBlock[], ts = 1): AcpMessage { |
| 45 | + return { |
| 46 | + type: "acp_message", |
| 47 | + ts, |
| 48 | + message: { |
| 49 | + jsonrpc: "2.0", |
| 50 | + id: ts, |
| 51 | + method: "session/prompt", |
| 52 | + params: { prompt }, |
| 53 | + }, |
| 54 | + }; |
| 55 | +} |
| 56 | + |
| 57 | +describe("extractUserPromptsFromEvents", () => { |
| 58 | + it("extracts text from a plain text prompt", () => { |
| 59 | + const events = [promptEvent([{ type: "text", text: "fix the bug" }])]; |
| 60 | + expect(extractUserPromptsFromEvents(events)).toEqual(["fix the bug"]); |
| 61 | + }); |
| 62 | + |
| 63 | + it("skips hidden text blocks", () => { |
| 64 | + const events = [ |
| 65 | + promptEvent([ |
| 66 | + { |
| 67 | + type: "text", |
| 68 | + text: "hidden context", |
| 69 | + _meta: { ui: { hidden: true } }, |
| 70 | + } as ContentBlock, |
| 71 | + { type: "text", text: "visible prompt" }, |
| 72 | + ]), |
| 73 | + ]; |
| 74 | + expect(extractUserPromptsFromEvents(events)).toEqual(["visible prompt"]); |
| 75 | + }); |
| 76 | + |
| 77 | + it("returns attachment labels when prompt has no text", () => { |
| 78 | + const uri = makeAttachmentUri("/tmp/screenshot.png"); |
| 79 | + const events = [ |
| 80 | + promptEvent([ |
| 81 | + { |
| 82 | + type: "resource", |
| 83 | + resource: { uri, text: "", mimeType: "image/png" }, |
| 84 | + }, |
| 85 | + ]), |
| 86 | + ]; |
| 87 | + expect(extractUserPromptsFromEvents(events)).toEqual([ |
| 88 | + "[Attached files: screenshot.png]", |
| 89 | + ]); |
| 90 | + }); |
| 91 | + |
| 92 | + it("returns text when prompt has both text and attachments", () => { |
| 93 | + const uri = makeAttachmentUri("/tmp/data.csv"); |
| 94 | + const events = [ |
| 95 | + promptEvent([ |
| 96 | + { type: "text", text: "analyze this" }, |
| 97 | + { type: "resource", resource: { uri, text: "", mimeType: "text/csv" } }, |
| 98 | + ]), |
| 99 | + ]; |
| 100 | + expect(extractUserPromptsFromEvents(events)).toEqual(["analyze this"]); |
| 101 | + }); |
| 102 | + |
| 103 | + it("joins multiple attachment labels with commas", () => { |
| 104 | + const uri1 = makeAttachmentUri("/tmp/a.png"); |
| 105 | + const uri2 = makeAttachmentUri("/tmp/b.pdf"); |
| 106 | + const events = [ |
| 107 | + promptEvent([ |
| 108 | + { |
| 109 | + type: "resource", |
| 110 | + resource: { uri: uri1, text: "", mimeType: "image/png" }, |
| 111 | + }, |
| 112 | + { |
| 113 | + type: "resource", |
| 114 | + resource: { uri: uri2, text: "", mimeType: "application/pdf" }, |
| 115 | + }, |
| 116 | + ]), |
| 117 | + ]; |
| 118 | + expect(extractUserPromptsFromEvents(events)).toEqual([ |
| 119 | + "[Attached files: a.png, b.pdf]", |
| 120 | + ]); |
| 121 | + }); |
| 122 | + |
| 123 | + it("falls back to attachment labels when all text blocks are hidden", () => { |
| 124 | + const uri = makeAttachmentUri("/tmp/report.md"); |
| 125 | + const events = [ |
| 126 | + promptEvent([ |
| 127 | + { |
| 128 | + type: "text", |
| 129 | + text: "hidden", |
| 130 | + _meta: { ui: { hidden: true } }, |
| 131 | + } as ContentBlock, |
| 132 | + { |
| 133 | + type: "resource", |
| 134 | + resource: { uri, text: "", mimeType: "text/markdown" }, |
| 135 | + }, |
| 136 | + ]), |
| 137 | + ]; |
| 138 | + expect(extractUserPromptsFromEvents(events)).toEqual([ |
| 139 | + "[Attached files: report.md]", |
| 140 | + ]); |
| 141 | + }); |
| 142 | + |
| 143 | + it("skips events with empty prompt arrays", () => { |
| 144 | + const events = [promptEvent([])]; |
| 145 | + expect(extractUserPromptsFromEvents(events)).toEqual([]); |
| 146 | + }); |
| 147 | + |
| 148 | + it("collects prompts from multiple events in order", () => { |
| 149 | + const uri = makeAttachmentUri("/tmp/logo.svg"); |
| 150 | + const events = [ |
| 151 | + promptEvent([{ type: "text", text: "first" }], 1), |
| 152 | + promptEvent( |
| 153 | + [ |
| 154 | + { |
| 155 | + type: "resource", |
| 156 | + resource: { uri, text: "", mimeType: "image/svg+xml" }, |
| 157 | + }, |
| 158 | + ], |
| 159 | + 2, |
| 160 | + ), |
| 161 | + promptEvent([{ type: "text", text: "third" }], 3), |
| 162 | + ]; |
| 163 | + expect(extractUserPromptsFromEvents(events)).toEqual([ |
| 164 | + "first", |
| 165 | + "[Attached files: logo.svg]", |
| 166 | + "third", |
| 167 | + ]); |
| 168 | + }); |
| 169 | +}); |
0 commit comments