@stonerzju/opencode 1.2.17 → 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.
- package/bin/opencode +29 -157
- package/package.json +29 -29
- package/src/acp/agent.ts +4 -4
- package/src/acp/session.ts +1 -1
- package/src/agent/agent.ts +3 -3
- package/src/bun/index.ts +2 -2
- package/src/cli/cmd/acp.ts +3 -3
- package/src/cli/cmd/debug/file.ts +1 -1
- package/src/cli/cmd/github.ts +2 -2
- package/src/cli/cmd/pr.ts +1 -1
- package/src/cli/cmd/tui/app.tsx +24 -24
- package/src/cli/cmd/tui/attach.ts +3 -3
- package/src/cli/cmd/tui/component/dialog-agent.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-command.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +5 -5
- package/src/cli/cmd/tui/component/dialog-model.tsx +4 -4
- package/src/cli/cmd/tui/component/dialog-provider.tsx +4 -4
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +5 -5
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-skill.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-stash.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-status.tsx +2 -2
- package/src/cli/cmd/tui/component/dialog-tag.tsx +3 -3
- package/src/cli/cmd/tui/component/logo.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +6 -6
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/history.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/index.tsx +14 -14
- package/src/cli/cmd/tui/component/prompt/stash.tsx +2 -2
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +1 -1
- package/src/cli/cmd/tui/component/tips.tsx +1 -1
- package/src/cli/cmd/tui/context/directory.ts +1 -1
- package/src/cli/cmd/tui/context/exit.tsx +1 -1
- package/src/cli/cmd/tui/context/keybind.tsx +2 -2
- package/src/cli/cmd/tui/context/kv.tsx +2 -2
- package/src/cli/cmd/tui/context/local.tsx +6 -6
- package/src/cli/cmd/tui/context/sync.tsx +4 -4
- package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
- package/src/cli/cmd/tui/context/theme.tsx +2 -2
- package/src/cli/cmd/tui/context/tui-config.tsx +1 -1
- package/src/cli/cmd/tui/event.ts +2 -2
- package/src/cli/cmd/tui/routes/home.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +2 -2
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +3 -3
- package/src/cli/cmd/tui/routes/session/header.tsx +5 -5
- package/src/cli/cmd/tui/routes/session/index.tsx +32 -32
- package/src/cli/cmd/tui/routes/session/permission.tsx +4 -4
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +4 -4
- package/src/cli/cmd/tui/thread.ts +9 -9
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +1 -1
- package/src/cli/cmd/tui/ui/dialog-help.tsx +2 -2
- package/src/cli/cmd/tui/ui/dialog-select.tsx +5 -5
- package/src/cli/cmd/tui/ui/dialog.tsx +3 -3
- package/src/cli/cmd/tui/ui/toast.tsx +1 -1
- package/src/cli/cmd/tui/util/editor.ts +3 -3
- package/src/cli/cmd/tui/util/transcript.ts +1 -1
- package/src/cli/cmd/tui/worker.ts +10 -10
- package/src/cli/error.ts +1 -1
- package/src/cli/ui.ts +1 -1
- package/src/cli/upgrade.ts +4 -4
- package/src/command/index.ts +1 -1
- package/src/config/config.ts +10 -10
- package/src/config/markdown.ts +1 -1
- package/src/config/migrate-tui-config.ts +5 -5
- package/src/config/paths.ts +4 -4
- package/src/config/tui.ts +4 -4
- package/src/control/control.sql.ts +1 -1
- package/src/control/index.ts +1 -1
- package/src/control-plane/adaptors/worktree.ts +1 -1
- package/src/control-plane/session-proxy-middleware.ts +1 -1
- package/src/control-plane/workspace.sql.ts +1 -1
- package/src/control-plane/workspace.ts +7 -7
- package/src/file/index.ts +1 -1
- package/src/file/ripgrep.ts +2 -2
- package/src/file/watcher.ts +5 -5
- package/src/format/formatter.ts +1 -1
- package/src/ide/index.ts +3 -3
- package/src/index.ts +1 -1
- package/src/installation/index.ts +3 -3
- package/src/lsp/client.ts +3 -3
- package/src/lsp/index.ts +3 -3
- package/src/mcp/index.ts +4 -4
- package/src/permission/index.ts +2 -2
- package/src/permission/next.ts +10 -10
- package/src/plugin/codex.ts +1 -1
- package/src/plugin/copilot.ts +2 -2
- package/src/plugin/index.ts +1 -1
- package/src/project/bootstrap.ts +2 -2
- package/src/project/instance.ts +4 -4
- package/src/project/project.sql.ts +1 -1
- package/src/project/project.ts +5 -5
- package/src/project/state.ts +1 -1
- package/src/project/vcs.ts +4 -4
- package/src/provider/auth.ts +4 -4
- package/src/provider/error.ts +1 -1
- package/src/provider/models-snapshot.ts +2 -0
- package/src/provider/models.ts +1 -1
- package/src/provider/provider.ts +2 -2
- package/src/provider/transform.ts +2 -2
- package/src/pty/index.ts +5 -5
- package/src/question/index.ts +5 -5
- package/src/server/event.ts +1 -1
- package/src/server/mdns.ts +1 -1
- package/src/server/routes/global.ts +3 -3
- package/src/server/routes/permission.ts +1 -1
- package/src/server/routes/pty.ts +1 -1
- package/src/server/routes/session.ts +4 -4
- package/src/server/routes/tui.ts +1 -1
- package/src/server/server.ts +3 -3
- package/src/session/compaction.ts +7 -7
- package/src/session/index.ts +10 -10
- package/src/session/instruction.ts +1 -1
- package/src/session/llm.ts +11 -11
- package/src/session/message-v2.ts +10 -10
- package/src/session/message.ts +1 -1
- package/src/session/processor.ts +10 -10
- package/src/session/prompt.ts +8 -8
- package/src/session/retry.ts +2 -2
- package/src/session/revert.ts +1 -1
- package/src/session/session.sql.ts +3 -3
- package/src/session/status.ts +3 -3
- package/src/session/summary.ts +5 -5
- package/src/session/system.ts +1 -1
- package/src/session/todo.ts +2 -2
- package/src/share/share-next.ts +7 -7
- package/src/share/share.sql.ts +1 -1
- package/src/shell/shell.ts +3 -3
- package/src/skill/skill.ts +6 -6
- package/src/storage/db.ts +1 -1
- package/src/storage/storage.ts +1 -1
- package/src/tool/bash.ts +6 -6
- package/src/tool/edit.ts +1 -1
- package/src/tool/registry.ts +2 -2
- package/src/tool/skill.ts +1 -1
- package/src/tool/task.ts +3 -3
- package/src/util/array.ts +10 -0
- package/src/util/binary.ts +41 -0
- package/src/util/encode.ts +51 -0
- package/src/util/error.ts +54 -0
- package/src/util/identifier.ts +48 -0
- package/src/util/lazy.ts +4 -16
- package/src/util/path.ts +37 -0
- package/src/util/retry.ts +41 -0
- package/src/util/slug.ts +74 -0
- package/src/worktree/index.ts +3 -3
- package/AGENTS.md +0 -10
- package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
- package/Dockerfile +0 -18
- package/README.md +0 -15
- package/bunfig.toml +0 -7
- package/drizzle.config.ts +0 -10
- package/script/build.ts +0 -224
- package/script/check-migrations.ts +0 -16
- package/script/postinstall.mjs +0 -131
- package/script/publish.ts +0 -181
- package/script/schema.ts +0 -63
- package/script/seed-e2e.ts +0 -50
- package/sst-env.d.ts +0 -10
- package/test/AGENTS.md +0 -81
- package/test/acp/agent-interface.test.ts +0 -51
- package/test/acp/event-subscription.test.ts +0 -683
- package/test/agent/agent.test.ts +0 -689
- package/test/bun.test.ts +0 -53
- package/test/cli/github-action.test.ts +0 -197
- package/test/cli/github-remote.test.ts +0 -80
- package/test/cli/import.test.ts +0 -38
- package/test/cli/plugin-auth-picker.test.ts +0 -120
- package/test/cli/tui/transcript.test.ts +0 -322
- package/test/config/agent-color.test.ts +0 -71
- package/test/config/config.test.ts +0 -1886
- package/test/config/fixtures/empty-frontmatter.md +0 -4
- package/test/config/fixtures/frontmatter.md +0 -28
- package/test/config/fixtures/markdown-header.md +0 -11
- package/test/config/fixtures/no-frontmatter.md +0 -1
- package/test/config/fixtures/weird-model-id.md +0 -13
- package/test/config/markdown.test.ts +0 -228
- package/test/config/tui.test.ts +0 -510
- package/test/control-plane/session-proxy-middleware.test.ts +0 -147
- package/test/control-plane/sse.test.ts +0 -56
- package/test/control-plane/workspace-server-sse.test.ts +0 -65
- package/test/control-plane/workspace-sync.test.ts +0 -97
- package/test/file/ignore.test.ts +0 -10
- package/test/file/index.test.ts +0 -394
- package/test/file/path-traversal.test.ts +0 -198
- package/test/file/ripgrep.test.ts +0 -39
- package/test/file/time.test.ts +0 -361
- package/test/fixture/db.ts +0 -11
- package/test/fixture/fixture.ts +0 -45
- package/test/fixture/lsp/fake-lsp-server.js +0 -77
- package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
- package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
- package/test/fixture/skills/cloudflare/SKILL.md +0 -211
- package/test/fixture/skills/index.json +0 -6
- package/test/ide/ide.test.ts +0 -82
- package/test/keybind.test.ts +0 -421
- package/test/lsp/client.test.ts +0 -95
- package/test/mcp/headers.test.ts +0 -153
- package/test/mcp/oauth-browser.test.ts +0 -249
- package/test/memory/abort-leak.test.ts +0 -136
- package/test/patch/patch.test.ts +0 -348
- package/test/permission/arity.test.ts +0 -33
- package/test/permission/next.test.ts +0 -689
- package/test/permission-task.test.ts +0 -319
- package/test/plugin/auth-override.test.ts +0 -44
- package/test/plugin/codex.test.ts +0 -123
- package/test/preload.ts +0 -80
- package/test/project/project.test.ts +0 -348
- package/test/project/worktree-remove.test.ts +0 -65
- package/test/provider/amazon-bedrock.test.ts +0 -446
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
- package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
- package/test/provider/gitlab-duo.test.ts +0 -262
- package/test/provider/provider.test.ts +0 -2220
- package/test/provider/transform.test.ts +0 -2353
- package/test/pty/pty-output-isolation.test.ts +0 -140
- package/test/question/question.test.ts +0 -300
- package/test/scheduler.test.ts +0 -73
- package/test/server/global-session-list.test.ts +0 -89
- package/test/server/session-list.test.ts +0 -90
- package/test/server/session-select.test.ts +0 -78
- package/test/session/compaction.test.ts +0 -423
- package/test/session/instruction.test.ts +0 -170
- package/test/session/llm.test.ts +0 -667
- package/test/session/message-v2.test.ts +0 -924
- package/test/session/prompt.test.ts +0 -211
- package/test/session/retry.test.ts +0 -188
- package/test/session/revert-compact.test.ts +0 -285
- package/test/session/session.test.ts +0 -71
- package/test/session/structured-output-integration.test.ts +0 -233
- package/test/session/structured-output.test.ts +0 -385
- package/test/skill/discovery.test.ts +0 -110
- package/test/skill/skill.test.ts +0 -388
- package/test/snapshot/snapshot.test.ts +0 -1180
- package/test/storage/json-migration.test.ts +0 -846
- package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
- package/test/tool/apply_patch.test.ts +0 -566
- package/test/tool/bash.test.ts +0 -402
- package/test/tool/edit.test.ts +0 -496
- package/test/tool/external-directory.test.ts +0 -127
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +0 -38413
- package/test/tool/grep.test.ts +0 -110
- package/test/tool/question.test.ts +0 -107
- package/test/tool/read.test.ts +0 -504
- package/test/tool/registry.test.ts +0 -122
- package/test/tool/skill.test.ts +0 -112
- package/test/tool/truncation.test.ts +0 -160
- package/test/tool/webfetch.test.ts +0 -100
- package/test/tool/write.test.ts +0 -348
- package/test/util/filesystem.test.ts +0 -443
- package/test/util/format.test.ts +0 -59
- package/test/util/glob.test.ts +0 -164
- package/test/util/iife.test.ts +0 -36
- package/test/util/lazy.test.ts +0 -50
- package/test/util/lock.test.ts +0 -72
- package/test/util/process.test.ts +0 -59
- package/test/util/timeout.test.ts +0 -21
- package/test/util/wildcard.test.ts +0 -90
- package/tsconfig.json +0 -16
package/test/session/llm.test.ts
DELETED
|
@@ -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
|
-
})
|