@stonerzju/opencode 1.2.17 → 1.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/bin/opencode +29 -157
  2. package/package.json +29 -29
  3. package/src/acp/agent.ts +4 -4
  4. package/src/acp/session.ts +1 -1
  5. package/src/agent/agent.ts +3 -3
  6. package/src/bun/index.ts +2 -2
  7. package/src/cli/cmd/acp.ts +3 -3
  8. package/src/cli/cmd/debug/file.ts +1 -1
  9. package/src/cli/cmd/github.ts +2 -2
  10. package/src/cli/cmd/pr.ts +1 -1
  11. package/src/cli/cmd/tui/app.tsx +24 -24
  12. package/src/cli/cmd/tui/attach.ts +3 -3
  13. package/src/cli/cmd/tui/component/dialog-agent.tsx +3 -3
  14. package/src/cli/cmd/tui/component/dialog-command.tsx +3 -3
  15. package/src/cli/cmd/tui/component/dialog-mcp.tsx +5 -5
  16. package/src/cli/cmd/tui/component/dialog-model.tsx +4 -4
  17. package/src/cli/cmd/tui/component/dialog-provider.tsx +4 -4
  18. package/src/cli/cmd/tui/component/dialog-session-list.tsx +5 -5
  19. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +3 -3
  20. package/src/cli/cmd/tui/component/dialog-skill.tsx +3 -3
  21. package/src/cli/cmd/tui/component/dialog-stash.tsx +3 -3
  22. package/src/cli/cmd/tui/component/dialog-status.tsx +2 -2
  23. package/src/cli/cmd/tui/component/dialog-tag.tsx +3 -3
  24. package/src/cli/cmd/tui/component/logo.tsx +2 -2
  25. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +6 -6
  26. package/src/cli/cmd/tui/component/prompt/frecency.tsx +2 -2
  27. package/src/cli/cmd/tui/component/prompt/history.tsx +2 -2
  28. package/src/cli/cmd/tui/component/prompt/index.tsx +14 -14
  29. package/src/cli/cmd/tui/component/prompt/stash.tsx +2 -2
  30. package/src/cli/cmd/tui/component/textarea-keybindings.ts +1 -1
  31. package/src/cli/cmd/tui/component/tips.tsx +1 -1
  32. package/src/cli/cmd/tui/context/directory.ts +1 -1
  33. package/src/cli/cmd/tui/context/exit.tsx +1 -1
  34. package/src/cli/cmd/tui/context/keybind.tsx +2 -2
  35. package/src/cli/cmd/tui/context/kv.tsx +2 -2
  36. package/src/cli/cmd/tui/context/local.tsx +6 -6
  37. package/src/cli/cmd/tui/context/sync.tsx +4 -4
  38. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  39. package/src/cli/cmd/tui/context/theme.tsx +2 -2
  40. package/src/cli/cmd/tui/context/tui-config.tsx +1 -1
  41. package/src/cli/cmd/tui/event.ts +2 -2
  42. package/src/cli/cmd/tui/routes/home.tsx +6 -6
  43. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +6 -6
  44. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +6 -6
  45. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +2 -2
  46. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +3 -3
  47. package/src/cli/cmd/tui/routes/session/header.tsx +5 -5
  48. package/src/cli/cmd/tui/routes/session/index.tsx +32 -32
  49. package/src/cli/cmd/tui/routes/session/permission.tsx +4 -4
  50. package/src/cli/cmd/tui/routes/session/sidebar.tsx +4 -4
  51. package/src/cli/cmd/tui/thread.ts +9 -9
  52. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +1 -1
  53. package/src/cli/cmd/tui/ui/dialog-help.tsx +2 -2
  54. package/src/cli/cmd/tui/ui/dialog-select.tsx +5 -5
  55. package/src/cli/cmd/tui/ui/dialog.tsx +3 -3
  56. package/src/cli/cmd/tui/ui/toast.tsx +1 -1
  57. package/src/cli/cmd/tui/util/editor.ts +3 -3
  58. package/src/cli/cmd/tui/util/transcript.ts +1 -1
  59. package/src/cli/cmd/tui/worker.ts +10 -10
  60. package/src/cli/error.ts +1 -1
  61. package/src/cli/ui.ts +1 -1
  62. package/src/cli/upgrade.ts +4 -4
  63. package/src/command/index.ts +1 -1
  64. package/src/config/config.ts +10 -10
  65. package/src/config/markdown.ts +1 -1
  66. package/src/config/migrate-tui-config.ts +5 -5
  67. package/src/config/paths.ts +4 -4
  68. package/src/config/tui.ts +4 -4
  69. package/src/control/control.sql.ts +1 -1
  70. package/src/control/index.ts +1 -1
  71. package/src/control-plane/adaptors/worktree.ts +1 -1
  72. package/src/control-plane/session-proxy-middleware.ts +1 -1
  73. package/src/control-plane/workspace.sql.ts +1 -1
  74. package/src/control-plane/workspace.ts +7 -7
  75. package/src/file/index.ts +1 -1
  76. package/src/file/ripgrep.ts +2 -2
  77. package/src/file/watcher.ts +5 -5
  78. package/src/format/formatter.ts +1 -1
  79. package/src/ide/index.ts +3 -3
  80. package/src/index.ts +1 -1
  81. package/src/installation/index.ts +3 -3
  82. package/src/lsp/client.ts +3 -3
  83. package/src/lsp/index.ts +3 -3
  84. package/src/mcp/index.ts +4 -4
  85. package/src/permission/index.ts +2 -2
  86. package/src/permission/next.ts +10 -10
  87. package/src/plugin/codex.ts +1 -1
  88. package/src/plugin/copilot.ts +2 -2
  89. package/src/plugin/index.ts +1 -1
  90. package/src/project/bootstrap.ts +2 -2
  91. package/src/project/instance.ts +4 -4
  92. package/src/project/project.sql.ts +1 -1
  93. package/src/project/project.ts +5 -5
  94. package/src/project/state.ts +1 -1
  95. package/src/project/vcs.ts +4 -4
  96. package/src/provider/auth.ts +4 -4
  97. package/src/provider/error.ts +1 -1
  98. package/src/provider/models-snapshot.ts +2 -0
  99. package/src/provider/models.ts +1 -1
  100. package/src/provider/provider.ts +2 -2
  101. package/src/provider/transform.ts +2 -2
  102. package/src/pty/index.ts +5 -5
  103. package/src/question/index.ts +5 -5
  104. package/src/server/event.ts +1 -1
  105. package/src/server/mdns.ts +1 -1
  106. package/src/server/routes/global.ts +3 -3
  107. package/src/server/routes/permission.ts +1 -1
  108. package/src/server/routes/pty.ts +1 -1
  109. package/src/server/routes/session.ts +4 -4
  110. package/src/server/routes/tui.ts +1 -1
  111. package/src/server/server.ts +3 -3
  112. package/src/session/compaction.ts +7 -7
  113. package/src/session/index.ts +10 -10
  114. package/src/session/instruction.ts +1 -1
  115. package/src/session/llm.ts +11 -11
  116. package/src/session/message-v2.ts +10 -10
  117. package/src/session/message.ts +1 -1
  118. package/src/session/processor.ts +10 -10
  119. package/src/session/prompt.ts +8 -8
  120. package/src/session/retry.ts +2 -2
  121. package/src/session/revert.ts +1 -1
  122. package/src/session/session.sql.ts +3 -3
  123. package/src/session/status.ts +3 -3
  124. package/src/session/summary.ts +5 -5
  125. package/src/session/system.ts +1 -1
  126. package/src/session/todo.ts +2 -2
  127. package/src/share/share-next.ts +7 -7
  128. package/src/share/share.sql.ts +1 -1
  129. package/src/shell/shell.ts +3 -3
  130. package/src/skill/skill.ts +6 -6
  131. package/src/storage/db.ts +1 -1
  132. package/src/storage/storage.ts +1 -1
  133. package/src/tool/bash.ts +6 -6
  134. package/src/tool/edit.ts +1 -1
  135. package/src/tool/registry.ts +2 -2
  136. package/src/tool/skill.ts +1 -1
  137. package/src/tool/task.ts +3 -3
  138. package/src/util/array.ts +10 -0
  139. package/src/util/binary.ts +41 -0
  140. package/src/util/encode.ts +51 -0
  141. package/src/util/error.ts +54 -0
  142. package/src/util/identifier.ts +48 -0
  143. package/src/util/lazy.ts +4 -16
  144. package/src/util/path.ts +37 -0
  145. package/src/util/retry.ts +41 -0
  146. package/src/util/slug.ts +74 -0
  147. package/src/worktree/index.ts +3 -3
  148. package/AGENTS.md +0 -10
  149. package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
  150. package/Dockerfile +0 -18
  151. package/README.md +0 -15
  152. package/bunfig.toml +0 -7
  153. package/drizzle.config.ts +0 -10
  154. package/script/build.ts +0 -224
  155. package/script/check-migrations.ts +0 -16
  156. package/script/postinstall.mjs +0 -131
  157. package/script/publish.ts +0 -181
  158. package/script/schema.ts +0 -63
  159. package/script/seed-e2e.ts +0 -50
  160. package/sst-env.d.ts +0 -10
  161. package/test/AGENTS.md +0 -81
  162. package/test/acp/agent-interface.test.ts +0 -51
  163. package/test/acp/event-subscription.test.ts +0 -683
  164. package/test/agent/agent.test.ts +0 -689
  165. package/test/bun.test.ts +0 -53
  166. package/test/cli/github-action.test.ts +0 -197
  167. package/test/cli/github-remote.test.ts +0 -80
  168. package/test/cli/import.test.ts +0 -38
  169. package/test/cli/plugin-auth-picker.test.ts +0 -120
  170. package/test/cli/tui/transcript.test.ts +0 -322
  171. package/test/config/agent-color.test.ts +0 -71
  172. package/test/config/config.test.ts +0 -1886
  173. package/test/config/fixtures/empty-frontmatter.md +0 -4
  174. package/test/config/fixtures/frontmatter.md +0 -28
  175. package/test/config/fixtures/markdown-header.md +0 -11
  176. package/test/config/fixtures/no-frontmatter.md +0 -1
  177. package/test/config/fixtures/weird-model-id.md +0 -13
  178. package/test/config/markdown.test.ts +0 -228
  179. package/test/config/tui.test.ts +0 -510
  180. package/test/control-plane/session-proxy-middleware.test.ts +0 -147
  181. package/test/control-plane/sse.test.ts +0 -56
  182. package/test/control-plane/workspace-server-sse.test.ts +0 -65
  183. package/test/control-plane/workspace-sync.test.ts +0 -97
  184. package/test/file/ignore.test.ts +0 -10
  185. package/test/file/index.test.ts +0 -394
  186. package/test/file/path-traversal.test.ts +0 -198
  187. package/test/file/ripgrep.test.ts +0 -39
  188. package/test/file/time.test.ts +0 -361
  189. package/test/fixture/db.ts +0 -11
  190. package/test/fixture/fixture.ts +0 -45
  191. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  192. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  193. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  194. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  195. package/test/fixture/skills/index.json +0 -6
  196. package/test/ide/ide.test.ts +0 -82
  197. package/test/keybind.test.ts +0 -421
  198. package/test/lsp/client.test.ts +0 -95
  199. package/test/mcp/headers.test.ts +0 -153
  200. package/test/mcp/oauth-browser.test.ts +0 -249
  201. package/test/memory/abort-leak.test.ts +0 -136
  202. package/test/patch/patch.test.ts +0 -348
  203. package/test/permission/arity.test.ts +0 -33
  204. package/test/permission/next.test.ts +0 -689
  205. package/test/permission-task.test.ts +0 -319
  206. package/test/plugin/auth-override.test.ts +0 -44
  207. package/test/plugin/codex.test.ts +0 -123
  208. package/test/preload.ts +0 -80
  209. package/test/project/project.test.ts +0 -348
  210. package/test/project/worktree-remove.test.ts +0 -65
  211. package/test/provider/amazon-bedrock.test.ts +0 -446
  212. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  213. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  214. package/test/provider/gitlab-duo.test.ts +0 -262
  215. package/test/provider/provider.test.ts +0 -2220
  216. package/test/provider/transform.test.ts +0 -2353
  217. package/test/pty/pty-output-isolation.test.ts +0 -140
  218. package/test/question/question.test.ts +0 -300
  219. package/test/scheduler.test.ts +0 -73
  220. package/test/server/global-session-list.test.ts +0 -89
  221. package/test/server/session-list.test.ts +0 -90
  222. package/test/server/session-select.test.ts +0 -78
  223. package/test/session/compaction.test.ts +0 -423
  224. package/test/session/instruction.test.ts +0 -170
  225. package/test/session/llm.test.ts +0 -667
  226. package/test/session/message-v2.test.ts +0 -924
  227. package/test/session/prompt.test.ts +0 -211
  228. package/test/session/retry.test.ts +0 -188
  229. package/test/session/revert-compact.test.ts +0 -285
  230. package/test/session/session.test.ts +0 -71
  231. package/test/session/structured-output-integration.test.ts +0 -233
  232. package/test/session/structured-output.test.ts +0 -385
  233. package/test/skill/discovery.test.ts +0 -110
  234. package/test/skill/skill.test.ts +0 -388
  235. package/test/snapshot/snapshot.test.ts +0 -1180
  236. package/test/storage/json-migration.test.ts +0 -846
  237. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  238. package/test/tool/apply_patch.test.ts +0 -566
  239. package/test/tool/bash.test.ts +0 -402
  240. package/test/tool/edit.test.ts +0 -496
  241. package/test/tool/external-directory.test.ts +0 -127
  242. package/test/tool/fixtures/large-image.png +0 -0
  243. package/test/tool/fixtures/models-api.json +0 -38413
  244. package/test/tool/grep.test.ts +0 -110
  245. package/test/tool/question.test.ts +0 -107
  246. package/test/tool/read.test.ts +0 -504
  247. package/test/tool/registry.test.ts +0 -122
  248. package/test/tool/skill.test.ts +0 -112
  249. package/test/tool/truncation.test.ts +0 -160
  250. package/test/tool/webfetch.test.ts +0 -100
  251. package/test/tool/write.test.ts +0 -348
  252. package/test/util/filesystem.test.ts +0 -443
  253. package/test/util/format.test.ts +0 -59
  254. package/test/util/glob.test.ts +0 -164
  255. package/test/util/iife.test.ts +0 -36
  256. package/test/util/lazy.test.ts +0 -50
  257. package/test/util/lock.test.ts +0 -72
  258. package/test/util/process.test.ts +0 -59
  259. package/test/util/timeout.test.ts +0 -21
  260. package/test/util/wildcard.test.ts +0 -90
  261. package/tsconfig.json +0 -16
@@ -1,667 +0,0 @@
1
- import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
2
- import path from "path"
3
- import type { ModelMessage } from "ai"
4
- import { LLM } from "../../src/session/llm"
5
- import { Global } from "../../src/global"
6
- import { Instance } from "../../src/project/instance"
7
- import { Provider } from "../../src/provider/provider"
8
- import { ProviderTransform } from "../../src/provider/transform"
9
- import { ModelsDev } from "../../src/provider/models"
10
- import { Filesystem } from "../../src/util/filesystem"
11
- import { tmpdir } from "../fixture/fixture"
12
- import type { Agent } from "../../src/agent/agent"
13
- import type { MessageV2 } from "../../src/session/message-v2"
14
-
15
- describe("session.llm.hasToolCalls", () => {
16
- test("returns false for empty messages array", () => {
17
- expect(LLM.hasToolCalls([])).toBe(false)
18
- })
19
-
20
- test("returns false for messages with only text content", () => {
21
- const messages: ModelMessage[] = [
22
- {
23
- role: "user",
24
- content: [{ type: "text", text: "Hello" }],
25
- },
26
- {
27
- role: "assistant",
28
- content: [{ type: "text", text: "Hi there" }],
29
- },
30
- ]
31
- expect(LLM.hasToolCalls(messages)).toBe(false)
32
- })
33
-
34
- test("returns true when messages contain tool-call", () => {
35
- const messages = [
36
- {
37
- role: "user",
38
- content: [{ type: "text", text: "Run a command" }],
39
- },
40
- {
41
- role: "assistant",
42
- content: [
43
- {
44
- type: "tool-call",
45
- toolCallId: "call-123",
46
- toolName: "bash",
47
- },
48
- ],
49
- },
50
- ] as ModelMessage[]
51
- expect(LLM.hasToolCalls(messages)).toBe(true)
52
- })
53
-
54
- test("returns true when messages contain tool-result", () => {
55
- const messages = [
56
- {
57
- role: "tool",
58
- content: [
59
- {
60
- type: "tool-result",
61
- toolCallId: "call-123",
62
- toolName: "bash",
63
- },
64
- ],
65
- },
66
- ] as ModelMessage[]
67
- expect(LLM.hasToolCalls(messages)).toBe(true)
68
- })
69
-
70
- test("returns false for messages with string content", () => {
71
- const messages: ModelMessage[] = [
72
- {
73
- role: "user",
74
- content: "Hello world",
75
- },
76
- {
77
- role: "assistant",
78
- content: "Hi there",
79
- },
80
- ]
81
- expect(LLM.hasToolCalls(messages)).toBe(false)
82
- })
83
-
84
- test("returns true when tool-call is mixed with text content", () => {
85
- const messages = [
86
- {
87
- role: "assistant",
88
- content: [
89
- { type: "text", text: "Let me run that command" },
90
- {
91
- type: "tool-call",
92
- toolCallId: "call-456",
93
- toolName: "read",
94
- },
95
- ],
96
- },
97
- ] as ModelMessage[]
98
- expect(LLM.hasToolCalls(messages)).toBe(true)
99
- })
100
- })
101
-
102
- type Capture = {
103
- url: URL
104
- headers: Headers
105
- body: Record<string, unknown>
106
- }
107
-
108
- const state = {
109
- server: null as ReturnType<typeof Bun.serve> | null,
110
- queue: [] as Array<{ path: string; response: Response; resolve: (value: Capture) => void }>,
111
- }
112
-
113
- function deferred<T>() {
114
- const result = {} as { promise: Promise<T>; resolve: (value: T) => void }
115
- result.promise = new Promise((resolve) => {
116
- result.resolve = resolve
117
- })
118
- return result
119
- }
120
-
121
- function waitRequest(pathname: string, response: Response) {
122
- const pending = deferred<Capture>()
123
- state.queue.push({ path: pathname, response, resolve: pending.resolve })
124
- return pending.promise
125
- }
126
-
127
- beforeAll(() => {
128
- state.server = Bun.serve({
129
- port: 0,
130
- async fetch(req) {
131
- const next = state.queue.shift()
132
- if (!next) {
133
- return new Response("unexpected request", { status: 500 })
134
- }
135
-
136
- const url = new URL(req.url)
137
- const body = (await req.json()) as Record<string, unknown>
138
- next.resolve({ url, headers: req.headers, body })
139
-
140
- if (!url.pathname.endsWith(next.path)) {
141
- return new Response("not found", { status: 404 })
142
- }
143
-
144
- return next.response
145
- },
146
- })
147
- })
148
-
149
- beforeEach(() => {
150
- state.queue.length = 0
151
- })
152
-
153
- afterAll(() => {
154
- state.server?.stop()
155
- })
156
-
157
- function createChatStream(text: string) {
158
- const payload =
159
- [
160
- `data: ${JSON.stringify({
161
- id: "chatcmpl-1",
162
- object: "chat.completion.chunk",
163
- choices: [{ delta: { role: "assistant" } }],
164
- })}`,
165
- `data: ${JSON.stringify({
166
- id: "chatcmpl-1",
167
- object: "chat.completion.chunk",
168
- choices: [{ delta: { content: text } }],
169
- })}`,
170
- `data: ${JSON.stringify({
171
- id: "chatcmpl-1",
172
- object: "chat.completion.chunk",
173
- choices: [{ delta: {}, finish_reason: "stop" }],
174
- })}`,
175
- "data: [DONE]",
176
- ].join("\n\n") + "\n\n"
177
-
178
- const encoder = new TextEncoder()
179
- return new ReadableStream<Uint8Array>({
180
- start(controller) {
181
- controller.enqueue(encoder.encode(payload))
182
- controller.close()
183
- },
184
- })
185
- }
186
-
187
- async function loadFixture(providerID: string, modelID: string) {
188
- const fixturePath = path.join(import.meta.dir, "../tool/fixtures/models-api.json")
189
- const data = await Filesystem.readJson<Record<string, ModelsDev.Provider>>(fixturePath)
190
- const provider = data[providerID]
191
- if (!provider) {
192
- throw new Error(`Missing provider in fixture: ${providerID}`)
193
- }
194
- const model = provider.models[modelID]
195
- if (!model) {
196
- throw new Error(`Missing model in fixture: ${modelID}`)
197
- }
198
- return { provider, model }
199
- }
200
-
201
- function createEventStream(chunks: unknown[], includeDone = false) {
202
- const lines = chunks.map((chunk) => `data: ${typeof chunk === "string" ? chunk : JSON.stringify(chunk)}`)
203
- if (includeDone) {
204
- lines.push("data: [DONE]")
205
- }
206
- const payload = lines.join("\n\n") + "\n\n"
207
- const encoder = new TextEncoder()
208
- return new ReadableStream<Uint8Array>({
209
- start(controller) {
210
- controller.enqueue(encoder.encode(payload))
211
- controller.close()
212
- },
213
- })
214
- }
215
-
216
- function createEventResponse(chunks: unknown[], includeDone = false) {
217
- return new Response(createEventStream(chunks, includeDone), {
218
- status: 200,
219
- headers: { "Content-Type": "text/event-stream" },
220
- })
221
- }
222
-
223
- describe("session.llm.stream", () => {
224
- test("sends temperature, tokens, and reasoning options for openai-compatible models", async () => {
225
- const server = state.server
226
- if (!server) {
227
- throw new Error("Server not initialized")
228
- }
229
-
230
- const providerID = "alibaba"
231
- const modelID = "qwen-plus"
232
- const fixture = await loadFixture(providerID, modelID)
233
- const provider = fixture.provider
234
- const model = fixture.model
235
-
236
- const request = waitRequest(
237
- "/chat/completions",
238
- new Response(createChatStream("Hello"), {
239
- status: 200,
240
- headers: { "Content-Type": "text/event-stream" },
241
- }),
242
- )
243
-
244
- await using tmp = await tmpdir({
245
- init: async (dir) => {
246
- await Bun.write(
247
- path.join(dir, "opencode.json"),
248
- JSON.stringify({
249
- $schema: "https://opencode.ai/config.json",
250
- enabled_providers: [providerID],
251
- provider: {
252
- [providerID]: {
253
- options: {
254
- apiKey: "test-key",
255
- baseURL: `${server.url.origin}/v1`,
256
- },
257
- },
258
- },
259
- }),
260
- )
261
- },
262
- })
263
-
264
- await Instance.provide({
265
- directory: tmp.path,
266
- fn: async () => {
267
- const resolved = await Provider.getModel(providerID, model.id)
268
- const sessionID = "session-test-1"
269
- const agent = {
270
- name: "test",
271
- mode: "primary",
272
- options: {},
273
- permission: [{ permission: "*", pattern: "*", action: "allow" }],
274
- temperature: 0.4,
275
- topP: 0.8,
276
- } satisfies Agent.Info
277
-
278
- const user = {
279
- id: "user-1",
280
- sessionID,
281
- role: "user",
282
- time: { created: Date.now() },
283
- agent: agent.name,
284
- model: { providerID, modelID: resolved.id },
285
- variant: "high",
286
- } satisfies MessageV2.User
287
-
288
- const stream = await LLM.stream({
289
- user,
290
- sessionID,
291
- model: resolved,
292
- agent,
293
- system: ["You are a helpful assistant."],
294
- abort: new AbortController().signal,
295
- messages: [{ role: "user", content: "Hello" }],
296
- tools: {},
297
- })
298
-
299
- for await (const _ of stream.fullStream) {
300
- }
301
-
302
- const capture = await request
303
- const body = capture.body
304
- const headers = capture.headers
305
- const url = capture.url
306
-
307
- expect(url.pathname.startsWith("/v1/")).toBe(true)
308
- expect(url.pathname.endsWith("/chat/completions")).toBe(true)
309
- expect(headers.get("Authorization")).toBe("Bearer test-key")
310
-
311
- expect(body.model).toBe(resolved.api.id)
312
- expect(body.temperature).toBe(0.4)
313
- expect(body.top_p).toBe(0.8)
314
- expect(body.stream).toBe(true)
315
-
316
- const maxTokens = (body.max_tokens as number | undefined) ?? (body.max_output_tokens as number | undefined)
317
- const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved)
318
- expect(maxTokens).toBe(expectedMaxTokens)
319
-
320
- const reasoning = (body.reasoningEffort as string | undefined) ?? (body.reasoning_effort as string | undefined)
321
- expect(reasoning).toBe("high")
322
- },
323
- })
324
- })
325
-
326
- test("sends responses API payload for OpenAI models", async () => {
327
- const server = state.server
328
- if (!server) {
329
- throw new Error("Server not initialized")
330
- }
331
-
332
- const source = await loadFixture("openai", "gpt-5.2")
333
- const model = source.model
334
-
335
- const responseChunks = [
336
- {
337
- type: "response.created",
338
- response: {
339
- id: "resp-1",
340
- created_at: Math.floor(Date.now() / 1000),
341
- model: model.id,
342
- service_tier: null,
343
- },
344
- },
345
- {
346
- type: "response.output_text.delta",
347
- item_id: "item-1",
348
- delta: "Hello",
349
- logprobs: null,
350
- },
351
- {
352
- type: "response.completed",
353
- response: {
354
- incomplete_details: null,
355
- usage: {
356
- input_tokens: 1,
357
- input_tokens_details: null,
358
- output_tokens: 1,
359
- output_tokens_details: null,
360
- },
361
- service_tier: null,
362
- },
363
- },
364
- ]
365
- const request = waitRequest("/responses", createEventResponse(responseChunks, true))
366
-
367
- await using tmp = await tmpdir({
368
- init: async (dir) => {
369
- await Bun.write(
370
- path.join(dir, "opencode.json"),
371
- JSON.stringify({
372
- $schema: "https://opencode.ai/config.json",
373
- enabled_providers: ["openai"],
374
- provider: {
375
- openai: {
376
- name: "OpenAI",
377
- env: ["OPENAI_API_KEY"],
378
- npm: "@ai-sdk/openai",
379
- api: "https://api.openai.com/v1",
380
- models: {
381
- [model.id]: model,
382
- },
383
- options: {
384
- apiKey: "test-openai-key",
385
- baseURL: `${server.url.origin}/v1`,
386
- },
387
- },
388
- },
389
- }),
390
- )
391
- },
392
- })
393
-
394
- await Instance.provide({
395
- directory: tmp.path,
396
- fn: async () => {
397
- const resolved = await Provider.getModel("openai", model.id)
398
- const sessionID = "session-test-2"
399
- const agent = {
400
- name: "test",
401
- mode: "primary",
402
- options: {},
403
- permission: [{ permission: "*", pattern: "*", action: "allow" }],
404
- temperature: 0.2,
405
- } satisfies Agent.Info
406
-
407
- const user = {
408
- id: "user-2",
409
- sessionID,
410
- role: "user",
411
- time: { created: Date.now() },
412
- agent: agent.name,
413
- model: { providerID: "openai", modelID: resolved.id },
414
- variant: "high",
415
- } satisfies MessageV2.User
416
-
417
- const stream = await LLM.stream({
418
- user,
419
- sessionID,
420
- model: resolved,
421
- agent,
422
- system: ["You are a helpful assistant."],
423
- abort: new AbortController().signal,
424
- messages: [{ role: "user", content: "Hello" }],
425
- tools: {},
426
- })
427
-
428
- for await (const _ of stream.fullStream) {
429
- }
430
-
431
- const capture = await request
432
- const body = capture.body
433
-
434
- expect(capture.url.pathname.endsWith("/responses")).toBe(true)
435
- expect(body.model).toBe(resolved.api.id)
436
- expect(body.stream).toBe(true)
437
- expect((body.reasoning as { effort?: string } | undefined)?.effort).toBe("high")
438
-
439
- const maxTokens = body.max_output_tokens as number | undefined
440
- const expectedMaxTokens = ProviderTransform.maxOutputTokens(resolved)
441
- expect(maxTokens).toBe(expectedMaxTokens)
442
- },
443
- })
444
- })
445
-
446
- test("sends messages API payload for Anthropic models", async () => {
447
- const server = state.server
448
- if (!server) {
449
- throw new Error("Server not initialized")
450
- }
451
-
452
- const providerID = "anthropic"
453
- const modelID = "claude-3-5-sonnet-20241022"
454
- const fixture = await loadFixture(providerID, modelID)
455
- const provider = fixture.provider
456
- const model = fixture.model
457
-
458
- const chunks = [
459
- {
460
- type: "message_start",
461
- message: {
462
- id: "msg-1",
463
- model: model.id,
464
- usage: {
465
- input_tokens: 3,
466
- cache_creation_input_tokens: null,
467
- cache_read_input_tokens: null,
468
- },
469
- },
470
- },
471
- {
472
- type: "content_block_start",
473
- index: 0,
474
- content_block: { type: "text", text: "" },
475
- },
476
- {
477
- type: "content_block_delta",
478
- index: 0,
479
- delta: { type: "text_delta", text: "Hello" },
480
- },
481
- { type: "content_block_stop", index: 0 },
482
- {
483
- type: "message_delta",
484
- delta: { stop_reason: "end_turn", stop_sequence: null, container: null },
485
- usage: {
486
- input_tokens: 3,
487
- output_tokens: 2,
488
- cache_creation_input_tokens: null,
489
- cache_read_input_tokens: null,
490
- },
491
- },
492
- { type: "message_stop" },
493
- ]
494
- const request = waitRequest("/messages", createEventResponse(chunks))
495
-
496
- await using tmp = await tmpdir({
497
- init: async (dir) => {
498
- await Bun.write(
499
- path.join(dir, "opencode.json"),
500
- JSON.stringify({
501
- $schema: "https://opencode.ai/config.json",
502
- enabled_providers: [providerID],
503
- provider: {
504
- [providerID]: {
505
- options: {
506
- apiKey: "test-anthropic-key",
507
- baseURL: `${server.url.origin}/v1`,
508
- },
509
- },
510
- },
511
- }),
512
- )
513
- },
514
- })
515
-
516
- await Instance.provide({
517
- directory: tmp.path,
518
- fn: async () => {
519
- const resolved = await Provider.getModel(providerID, model.id)
520
- const sessionID = "session-test-3"
521
- const agent = {
522
- name: "test",
523
- mode: "primary",
524
- options: {},
525
- permission: [{ permission: "*", pattern: "*", action: "allow" }],
526
- temperature: 0.4,
527
- topP: 0.9,
528
- } satisfies Agent.Info
529
-
530
- const user = {
531
- id: "user-3",
532
- sessionID,
533
- role: "user",
534
- time: { created: Date.now() },
535
- agent: agent.name,
536
- model: { providerID, modelID: resolved.id },
537
- } satisfies MessageV2.User
538
-
539
- const stream = await LLM.stream({
540
- user,
541
- sessionID,
542
- model: resolved,
543
- agent,
544
- system: ["You are a helpful assistant."],
545
- abort: new AbortController().signal,
546
- messages: [{ role: "user", content: "Hello" }],
547
- tools: {},
548
- })
549
-
550
- for await (const _ of stream.fullStream) {
551
- }
552
-
553
- const capture = await request
554
- const body = capture.body
555
-
556
- expect(capture.url.pathname.endsWith("/messages")).toBe(true)
557
- expect(body.model).toBe(resolved.api.id)
558
- expect(body.max_tokens).toBe(ProviderTransform.maxOutputTokens(resolved))
559
- expect(body.temperature).toBe(0.4)
560
- expect(body.top_p).toBe(0.9)
561
- },
562
- })
563
- })
564
-
565
- test("sends Google API payload for Gemini models", async () => {
566
- const server = state.server
567
- if (!server) {
568
- throw new Error("Server not initialized")
569
- }
570
-
571
- const providerID = "google"
572
- const modelID = "gemini-2.5-flash"
573
- const fixture = await loadFixture(providerID, modelID)
574
- const provider = fixture.provider
575
- const model = fixture.model
576
- const pathSuffix = `/v1beta/models/${model.id}:streamGenerateContent`
577
-
578
- const chunks = [
579
- {
580
- candidates: [
581
- {
582
- content: {
583
- parts: [{ text: "Hello" }],
584
- },
585
- finishReason: "STOP",
586
- },
587
- ],
588
- usageMetadata: {
589
- promptTokenCount: 1,
590
- candidatesTokenCount: 1,
591
- totalTokenCount: 2,
592
- },
593
- },
594
- ]
595
- const request = waitRequest(pathSuffix, createEventResponse(chunks))
596
-
597
- await using tmp = await tmpdir({
598
- init: async (dir) => {
599
- await Bun.write(
600
- path.join(dir, "opencode.json"),
601
- JSON.stringify({
602
- $schema: "https://opencode.ai/config.json",
603
- enabled_providers: [providerID],
604
- provider: {
605
- [providerID]: {
606
- options: {
607
- apiKey: "test-google-key",
608
- baseURL: `${server.url.origin}/v1beta`,
609
- },
610
- },
611
- },
612
- }),
613
- )
614
- },
615
- })
616
-
617
- await Instance.provide({
618
- directory: tmp.path,
619
- fn: async () => {
620
- const resolved = await Provider.getModel(providerID, model.id)
621
- const sessionID = "session-test-4"
622
- const agent = {
623
- name: "test",
624
- mode: "primary",
625
- options: {},
626
- permission: [{ permission: "*", pattern: "*", action: "allow" }],
627
- temperature: 0.3,
628
- topP: 0.8,
629
- } satisfies Agent.Info
630
-
631
- const user = {
632
- id: "user-4",
633
- sessionID,
634
- role: "user",
635
- time: { created: Date.now() },
636
- agent: agent.name,
637
- model: { providerID, modelID: resolved.id },
638
- } satisfies MessageV2.User
639
-
640
- const stream = await LLM.stream({
641
- user,
642
- sessionID,
643
- model: resolved,
644
- agent,
645
- system: ["You are a helpful assistant."],
646
- abort: new AbortController().signal,
647
- messages: [{ role: "user", content: "Hello" }],
648
- tools: {},
649
- })
650
-
651
- for await (const _ of stream.fullStream) {
652
- }
653
-
654
- const capture = await request
655
- const body = capture.body
656
- const config = body.generationConfig as
657
- | { temperature?: number; topP?: number; maxOutputTokens?: number }
658
- | undefined
659
-
660
- expect(capture.url.pathname).toBe(pathSuffix)
661
- expect(config?.temperature).toBe(0.3)
662
- expect(config?.topP).toBe(0.8)
663
- expect(config?.maxOutputTokens).toBe(ProviderTransform.maxOutputTokens(resolved))
664
- },
665
- })
666
- })
667
- })