forked from anomalyco/opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub-action.test.ts
More file actions
129 lines (114 loc) · 3.71 KB
/
github-action.test.ts
File metadata and controls
129 lines (114 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { test, expect, describe } from "bun:test"
import { extractResponseText } from "../../src/cli/cmd/github"
import type { MessageV2 } from "../../src/session/message-v2"
// Helper to create minimal valid parts
function createTextPart(text: string): MessageV2.Part {
return {
id: "1",
sessionID: "s",
messageID: "m",
type: "text" as const,
text,
}
}
function createReasoningPart(text: string): MessageV2.Part {
return {
id: "1",
sessionID: "s",
messageID: "m",
type: "reasoning" as const,
text,
time: { start: 0 },
}
}
function createToolPart(tool: string, title: string, status: "completed" | "running" = "completed"): MessageV2.Part {
if (status === "completed") {
return {
id: "1",
sessionID: "s",
messageID: "m",
type: "tool" as const,
callID: "c1",
tool,
state: {
status: "completed",
input: {},
output: "",
title,
metadata: {},
time: { start: 0, end: 1 },
},
}
}
return {
id: "1",
sessionID: "s",
messageID: "m",
type: "tool" as const,
callID: "c1",
tool,
state: {
status: "running",
input: {},
time: { start: 0 },
},
}
}
function createStepStartPart(): MessageV2.Part {
return {
id: "1",
sessionID: "s",
messageID: "m",
type: "step-start" as const,
}
}
describe("extractResponseText", () => {
test("returns text from text part", () => {
const parts = [createTextPart("Hello world")]
expect(extractResponseText(parts)).toBe("Hello world")
})
test("returns last text part when multiple exist", () => {
const parts = [createTextPart("First"), createTextPart("Last")]
expect(extractResponseText(parts)).toBe("Last")
})
test("returns text even when tool parts follow", () => {
const parts = [createTextPart("I'll help with that."), createToolPart("todowrite", "3 todos")]
expect(extractResponseText(parts)).toBe("I'll help with that.")
})
test("returns null for reasoning-only response (signals summary needed)", () => {
const parts = [createReasoningPart("Let me think about this...")]
expect(extractResponseText(parts)).toBeNull()
})
test("returns null for tool-only response (signals summary needed)", () => {
// This is the exact scenario from the bug report - todowrite with no text
const parts = [createToolPart("todowrite", "8 todos")]
expect(extractResponseText(parts)).toBeNull()
})
test("returns null for multiple completed tools", () => {
const parts = [
createToolPart("read", "src/file.ts"),
createToolPart("edit", "src/file.ts"),
createToolPart("bash", "bun test"),
]
expect(extractResponseText(parts)).toBeNull()
})
test("ignores running tool parts (throws since no completed tools)", () => {
const parts = [createToolPart("bash", "", "running")]
expect(() => extractResponseText(parts)).toThrow("Failed to parse response")
})
test("throws with part types on empty array", () => {
expect(() => extractResponseText([])).toThrow("Part types found: [none]")
})
test("throws with part types on unhandled parts", () => {
const parts = [createStepStartPart()]
expect(() => extractResponseText(parts)).toThrow("Part types found: [step-start]")
})
test("prefers text over reasoning when both present", () => {
const parts = [createReasoningPart("Internal thinking..."), createTextPart("Final answer")]
expect(extractResponseText(parts)).toBe("Final answer")
})
test("prefers text over tools when both present", () => {
const parts = [createToolPart("read", "src/file.ts"), createTextPart("Here's what I found")]
expect(extractResponseText(parts)).toBe("Here's what I found")
})
})