@stonerzju/opencode 1.2.16-offline.1 → 1.2.18

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 (262) 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/package.json.bak +0 -140
  155. package/script/build.ts +0 -224
  156. package/script/check-migrations.ts +0 -16
  157. package/script/postinstall.mjs +0 -131
  158. package/script/publish.ts +0 -181
  159. package/script/schema.ts +0 -63
  160. package/script/seed-e2e.ts +0 -50
  161. package/sst-env.d.ts +0 -10
  162. package/test/AGENTS.md +0 -81
  163. package/test/acp/agent-interface.test.ts +0 -51
  164. package/test/acp/event-subscription.test.ts +0 -683
  165. package/test/agent/agent.test.ts +0 -689
  166. package/test/bun.test.ts +0 -53
  167. package/test/cli/github-action.test.ts +0 -197
  168. package/test/cli/github-remote.test.ts +0 -80
  169. package/test/cli/import.test.ts +0 -38
  170. package/test/cli/plugin-auth-picker.test.ts +0 -120
  171. package/test/cli/tui/transcript.test.ts +0 -322
  172. package/test/config/agent-color.test.ts +0 -71
  173. package/test/config/config.test.ts +0 -1886
  174. package/test/config/fixtures/empty-frontmatter.md +0 -4
  175. package/test/config/fixtures/frontmatter.md +0 -28
  176. package/test/config/fixtures/markdown-header.md +0 -11
  177. package/test/config/fixtures/no-frontmatter.md +0 -1
  178. package/test/config/fixtures/weird-model-id.md +0 -13
  179. package/test/config/markdown.test.ts +0 -228
  180. package/test/config/tui.test.ts +0 -510
  181. package/test/control-plane/session-proxy-middleware.test.ts +0 -147
  182. package/test/control-plane/sse.test.ts +0 -56
  183. package/test/control-plane/workspace-server-sse.test.ts +0 -65
  184. package/test/control-plane/workspace-sync.test.ts +0 -97
  185. package/test/file/ignore.test.ts +0 -10
  186. package/test/file/index.test.ts +0 -394
  187. package/test/file/path-traversal.test.ts +0 -198
  188. package/test/file/ripgrep.test.ts +0 -39
  189. package/test/file/time.test.ts +0 -361
  190. package/test/fixture/db.ts +0 -11
  191. package/test/fixture/fixture.ts +0 -45
  192. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  193. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  194. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  195. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  196. package/test/fixture/skills/index.json +0 -6
  197. package/test/ide/ide.test.ts +0 -82
  198. package/test/keybind.test.ts +0 -421
  199. package/test/lsp/client.test.ts +0 -95
  200. package/test/mcp/headers.test.ts +0 -153
  201. package/test/mcp/oauth-browser.test.ts +0 -249
  202. package/test/memory/abort-leak.test.ts +0 -136
  203. package/test/patch/patch.test.ts +0 -348
  204. package/test/permission/arity.test.ts +0 -33
  205. package/test/permission/next.test.ts +0 -689
  206. package/test/permission-task.test.ts +0 -319
  207. package/test/plugin/auth-override.test.ts +0 -44
  208. package/test/plugin/codex.test.ts +0 -123
  209. package/test/preload.ts +0 -80
  210. package/test/project/project.test.ts +0 -348
  211. package/test/project/worktree-remove.test.ts +0 -65
  212. package/test/provider/amazon-bedrock.test.ts +0 -446
  213. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  214. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  215. package/test/provider/gitlab-duo.test.ts +0 -262
  216. package/test/provider/provider.test.ts +0 -2220
  217. package/test/provider/transform.test.ts +0 -2353
  218. package/test/pty/pty-output-isolation.test.ts +0 -140
  219. package/test/question/question.test.ts +0 -300
  220. package/test/scheduler.test.ts +0 -73
  221. package/test/server/global-session-list.test.ts +0 -89
  222. package/test/server/session-list.test.ts +0 -90
  223. package/test/server/session-select.test.ts +0 -78
  224. package/test/session/compaction.test.ts +0 -423
  225. package/test/session/instruction.test.ts +0 -170
  226. package/test/session/llm.test.ts +0 -667
  227. package/test/session/message-v2.test.ts +0 -924
  228. package/test/session/prompt.test.ts +0 -211
  229. package/test/session/retry.test.ts +0 -188
  230. package/test/session/revert-compact.test.ts +0 -285
  231. package/test/session/session.test.ts +0 -71
  232. package/test/session/structured-output-integration.test.ts +0 -233
  233. package/test/session/structured-output.test.ts +0 -385
  234. package/test/skill/discovery.test.ts +0 -110
  235. package/test/skill/skill.test.ts +0 -388
  236. package/test/snapshot/snapshot.test.ts +0 -1180
  237. package/test/storage/json-migration.test.ts +0 -846
  238. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  239. package/test/tool/apply_patch.test.ts +0 -566
  240. package/test/tool/bash.test.ts +0 -402
  241. package/test/tool/edit.test.ts +0 -496
  242. package/test/tool/external-directory.test.ts +0 -127
  243. package/test/tool/fixtures/large-image.png +0 -0
  244. package/test/tool/fixtures/models-api.json +0 -38413
  245. package/test/tool/grep.test.ts +0 -110
  246. package/test/tool/question.test.ts +0 -107
  247. package/test/tool/read.test.ts +0 -504
  248. package/test/tool/registry.test.ts +0 -122
  249. package/test/tool/skill.test.ts +0 -112
  250. package/test/tool/truncation.test.ts +0 -160
  251. package/test/tool/webfetch.test.ts +0 -100
  252. package/test/tool/write.test.ts +0 -348
  253. package/test/util/filesystem.test.ts +0 -443
  254. package/test/util/format.test.ts +0 -59
  255. package/test/util/glob.test.ts +0 -164
  256. package/test/util/iife.test.ts +0 -36
  257. package/test/util/lazy.test.ts +0 -50
  258. package/test/util/lock.test.ts +0 -72
  259. package/test/util/process.test.ts +0 -59
  260. package/test/util/timeout.test.ts +0 -21
  261. package/test/util/wildcard.test.ts +0 -90
  262. 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
- })