nuwaxcode 1.1.34 → 1.1.44
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/nuwaxcode +19 -1
- package/package.json +15 -117
- package/{script/postinstall.mjs → postinstall.mjs} +18 -6
- package/AGENTS.md +0 -27
- package/Dockerfile +0 -18
- package/README.md +0 -15
- package/bunfig.toml +0 -7
- package/parsers-config.ts +0 -253
- package/script/build.ts +0 -172
- package/script/publish-registries.ts +0 -187
- package/script/publish.ts +0 -70
- package/script/schema.ts +0 -47
- package/src/acp/README.md +0 -164
- package/src/acp/agent.ts +0 -1280
- package/src/acp/session.ts +0 -111
- package/src/acp/types.ts +0 -24
- package/src/agent/agent.ts +0 -332
- package/src/agent/generate.txt +0 -75
- package/src/agent/prompt/compaction.txt +0 -12
- package/src/agent/prompt/explore.txt +0 -18
- package/src/agent/prompt/summary.txt +0 -11
- package/src/agent/prompt/title.txt +0 -43
- package/src/auth/index.ts +0 -73
- package/src/bun/index.ts +0 -134
- package/src/bus/bus-event.ts +0 -43
- package/src/bus/global.ts +0 -10
- package/src/bus/index.ts +0 -105
- package/src/cli/bootstrap.ts +0 -17
- package/src/cli/cmd/acp.ts +0 -69
- package/src/cli/cmd/agent.ts +0 -257
- package/src/cli/cmd/auth.ts +0 -400
- package/src/cli/cmd/cmd.ts +0 -7
- package/src/cli/cmd/debug/agent.ts +0 -166
- package/src/cli/cmd/debug/config.ts +0 -16
- package/src/cli/cmd/debug/file.ts +0 -97
- package/src/cli/cmd/debug/index.ts +0 -48
- package/src/cli/cmd/debug/lsp.ts +0 -52
- package/src/cli/cmd/debug/ripgrep.ts +0 -87
- package/src/cli/cmd/debug/scrap.ts +0 -16
- package/src/cli/cmd/debug/skill.ts +0 -16
- package/src/cli/cmd/debug/snapshot.ts +0 -52
- package/src/cli/cmd/export.ts +0 -88
- package/src/cli/cmd/generate.ts +0 -38
- package/src/cli/cmd/github.ts +0 -1548
- package/src/cli/cmd/import.ts +0 -98
- package/src/cli/cmd/mcp.ts +0 -755
- package/src/cli/cmd/models.ts +0 -77
- package/src/cli/cmd/pr.ts +0 -112
- package/src/cli/cmd/run.ts +0 -395
- package/src/cli/cmd/serve.ts +0 -20
- package/src/cli/cmd/session.ts +0 -135
- package/src/cli/cmd/stats.ts +0 -402
- package/src/cli/cmd/tui/app.tsx +0 -761
- package/src/cli/cmd/tui/attach.ts +0 -31
- package/src/cli/cmd/tui/component/border.tsx +0 -21
- package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
- package/src/cli/cmd/tui/component/dialog-command.tsx +0 -148
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +0 -234
- package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -256
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -114
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
- package/src/cli/cmd/tui/component/dialog-stash.tsx +0 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +0 -164
- package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
- package/src/cli/cmd/tui/component/logo.tsx +0 -88
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -632
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +0 -89
- package/src/cli/cmd/tui/component/prompt/history.tsx +0 -108
- package/src/cli/cmd/tui/component/prompt/index.tsx +0 -1096
- package/src/cli/cmd/tui/component/prompt/stash.tsx +0 -101
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +0 -73
- package/src/cli/cmd/tui/component/tips.tsx +0 -153
- package/src/cli/cmd/tui/component/todo-item.tsx +0 -32
- package/src/cli/cmd/tui/context/args.tsx +0 -14
- package/src/cli/cmd/tui/context/directory.ts +0 -13
- package/src/cli/cmd/tui/context/exit.tsx +0 -23
- package/src/cli/cmd/tui/context/helper.tsx +0 -25
- package/src/cli/cmd/tui/context/keybind.tsx +0 -101
- package/src/cli/cmd/tui/context/kv.tsx +0 -52
- package/src/cli/cmd/tui/context/local.tsx +0 -402
- package/src/cli/cmd/tui/context/prompt.tsx +0 -18
- package/src/cli/cmd/tui/context/route.tsx +0 -46
- package/src/cli/cmd/tui/context/sdk.tsx +0 -94
- package/src/cli/cmd/tui/context/sync.tsx +0 -427
- package/src/cli/cmd/tui/context/theme/aura.json +0 -69
- package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
- package/src/cli/cmd/tui/context/theme/carbonfox.json +0 -248
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +0 -233
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -233
- package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
- package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -228
- package/src/cli/cmd/tui/context/theme/cursor.json +0 -249
- package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
- package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
- package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
- package/src/cli/cmd/tui/context/theme/github.json +0 -233
- package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -95
- package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +0 -237
- package/src/cli/cmd/tui/context/theme/material.json +0 -235
- package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
- package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
- package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
- package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
- package/src/cli/cmd/tui/context/theme/nord.json +0 -223
- package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
- package/src/cli/cmd/tui/context/theme/orng.json +0 -249
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +0 -93
- package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
- package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
- package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
- package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
- package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
- package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
- package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
- package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
- package/src/cli/cmd/tui/context/theme.tsx +0 -1152
- package/src/cli/cmd/tui/event.ts +0 -48
- package/src/cli/cmd/tui/routes/home.tsx +0 -140
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +0 -64
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -109
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +0 -26
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -47
- package/src/cli/cmd/tui/routes/session/footer.tsx +0 -91
- package/src/cli/cmd/tui/routes/session/header.tsx +0 -136
- package/src/cli/cmd/tui/routes/session/index.tsx +0 -2050
- package/src/cli/cmd/tui/routes/session/permission.tsx +0 -495
- package/src/cli/cmd/tui/routes/session/question.tsx +0 -435
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -312
- package/src/cli/cmd/tui/thread.ts +0 -165
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -57
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -83
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +0 -204
- package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -38
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -77
- package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -354
- package/src/cli/cmd/tui/ui/dialog.tsx +0 -167
- package/src/cli/cmd/tui/ui/link.tsx +0 -28
- package/src/cli/cmd/tui/ui/spinner.ts +0 -368
- package/src/cli/cmd/tui/ui/toast.tsx +0 -100
- package/src/cli/cmd/tui/util/clipboard.ts +0 -160
- package/src/cli/cmd/tui/util/editor.ts +0 -32
- package/src/cli/cmd/tui/util/signal.ts +0 -7
- package/src/cli/cmd/tui/util/terminal.ts +0 -114
- package/src/cli/cmd/tui/util/transcript.ts +0 -98
- package/src/cli/cmd/tui/worker.ts +0 -152
- package/src/cli/cmd/uninstall.ts +0 -357
- package/src/cli/cmd/upgrade.ts +0 -73
- package/src/cli/cmd/web.ts +0 -81
- package/src/cli/error.ts +0 -57
- package/src/cli/network.ts +0 -53
- package/src/cli/ui.ts +0 -84
- package/src/cli/upgrade.ts +0 -25
- package/src/command/index.ts +0 -131
- package/src/command/template/initialize.txt +0 -10
- package/src/command/template/review.txt +0 -99
- package/src/config/config.ts +0 -1255
- package/src/config/markdown.ts +0 -93
- package/src/env/index.ts +0 -26
- package/src/file/ignore.ts +0 -83
- package/src/file/index.ts +0 -411
- package/src/file/ripgrep.ts +0 -409
- package/src/file/time.ts +0 -64
- package/src/file/watcher.ts +0 -118
- package/src/flag/flag.ts +0 -54
- package/src/format/formatter.ts +0 -359
- package/src/format/index.ts +0 -137
- package/src/global/index.ts +0 -55
- package/src/id/id.ts +0 -83
- package/src/ide/index.ts +0 -76
- package/src/index.ts +0 -159
- package/src/installation/index.ts +0 -246
- package/src/lsp/client.ts +0 -252
- package/src/lsp/index.ts +0 -485
- package/src/lsp/language.ts +0 -119
- package/src/lsp/server.ts +0 -2046
- package/src/mcp/auth.ts +0 -135
- package/src/mcp/index.ts +0 -926
- package/src/mcp/oauth-callback.ts +0 -200
- package/src/mcp/oauth-provider.ts +0 -154
- package/src/patch/index.ts +0 -680
- package/src/permission/arity.ts +0 -163
- package/src/permission/index.ts +0 -210
- package/src/permission/next.ts +0 -269
- package/src/plugin/codex.ts +0 -493
- package/src/plugin/copilot.ts +0 -269
- package/src/plugin/index.ts +0 -135
- package/src/project/bootstrap.ts +0 -35
- package/src/project/instance.ts +0 -91
- package/src/project/project.ts +0 -320
- package/src/project/state.ts +0 -66
- package/src/project/vcs.ts +0 -76
- package/src/provider/auth.ts +0 -147
- package/src/provider/models-macro.ts +0 -11
- package/src/provider/models.ts +0 -112
- package/src/provider/provider.ts +0 -1219
- package/src/provider/sdk/openai-compatible/src/README.md +0 -5
- package/src/provider/sdk/openai-compatible/src/index.ts +0 -2
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +0 -100
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +0 -303
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +0 -22
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +0 -18
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +0 -22
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +0 -207
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +0 -1732
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +0 -177
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +0 -1
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +0 -88
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +0 -128
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +0 -115
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +0 -65
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +0 -104
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +0 -103
- package/src/provider/transform.ts +0 -724
- package/src/pty/index.ts +0 -229
- package/src/question/index.ts +0 -171
- package/src/scheduler/index.ts +0 -61
- package/src/server/error.ts +0 -36
- package/src/server/mdns.ts +0 -59
- package/src/server/routes/config.ts +0 -92
- package/src/server/routes/experimental.ts +0 -157
- package/src/server/routes/file.ts +0 -197
- package/src/server/routes/global.ts +0 -135
- package/src/server/routes/mcp.ts +0 -225
- package/src/server/routes/permission.ts +0 -68
- package/src/server/routes/project.ts +0 -82
- package/src/server/routes/provider.ts +0 -165
- package/src/server/routes/pty.ts +0 -169
- package/src/server/routes/question.ts +0 -98
- package/src/server/routes/session.ts +0 -935
- package/src/server/routes/tui.ts +0 -379
- package/src/server/server.ts +0 -578
- package/src/session/compaction.ts +0 -225
- package/src/session/index.ts +0 -488
- package/src/session/llm.ts +0 -279
- package/src/session/message-v2.ts +0 -702
- package/src/session/message.ts +0 -189
- package/src/session/processor.ts +0 -406
- package/src/session/prompt/anthropic-20250930.txt +0 -166
- package/src/session/prompt/anthropic.txt +0 -105
- package/src/session/prompt/anthropic_spoof.txt +0 -1
- package/src/session/prompt/beast.txt +0 -147
- package/src/session/prompt/build-switch.txt +0 -5
- package/src/session/prompt/codex.txt +0 -73
- package/src/session/prompt/codex_header.txt +0 -72
- package/src/session/prompt/copilot-gpt-5.txt +0 -143
- package/src/session/prompt/gemini.txt +0 -155
- package/src/session/prompt/max-steps.txt +0 -16
- package/src/session/prompt/plan-reminder-anthropic.txt +0 -67
- package/src/session/prompt/plan.txt +0 -26
- package/src/session/prompt/qwen.txt +0 -109
- package/src/session/prompt.ts +0 -1805
- package/src/session/retry.ts +0 -90
- package/src/session/revert.ts +0 -108
- package/src/session/status.ts +0 -76
- package/src/session/summary.ts +0 -150
- package/src/session/system.ts +0 -138
- package/src/session/todo.ts +0 -37
- package/src/share/share-next.ts +0 -194
- package/src/share/share.ts +0 -87
- package/src/shell/shell.ts +0 -67
- package/src/skill/index.ts +0 -1
- package/src/skill/skill.ts +0 -136
- package/src/snapshot/index.ts +0 -236
- package/src/storage/storage.ts +0 -227
- package/src/tool/apply_patch.ts +0 -277
- package/src/tool/apply_patch.txt +0 -1
- package/src/tool/bash.ts +0 -258
- package/src/tool/bash.txt +0 -115
- package/src/tool/batch.ts +0 -175
- package/src/tool/batch.txt +0 -24
- package/src/tool/codesearch.ts +0 -132
- package/src/tool/codesearch.txt +0 -12
- package/src/tool/edit.ts +0 -645
- package/src/tool/edit.txt +0 -10
- package/src/tool/external-directory.ts +0 -32
- package/src/tool/glob.ts +0 -77
- package/src/tool/glob.txt +0 -6
- package/src/tool/grep.ts +0 -154
- package/src/tool/grep.txt +0 -8
- package/src/tool/invalid.ts +0 -17
- package/src/tool/ls.ts +0 -121
- package/src/tool/ls.txt +0 -1
- package/src/tool/lsp.ts +0 -96
- package/src/tool/lsp.txt +0 -19
- package/src/tool/multiedit.ts +0 -46
- package/src/tool/multiedit.txt +0 -41
- package/src/tool/plan-enter.txt +0 -14
- package/src/tool/plan-exit.txt +0 -13
- package/src/tool/plan.ts +0 -130
- package/src/tool/question.ts +0 -33
- package/src/tool/question.txt +0 -10
- package/src/tool/read.ts +0 -202
- package/src/tool/read.txt +0 -12
- package/src/tool/registry.ts +0 -158
- package/src/tool/skill.ts +0 -75
- package/src/tool/task.ts +0 -188
- package/src/tool/task.txt +0 -60
- package/src/tool/todo.ts +0 -53
- package/src/tool/todoread.txt +0 -14
- package/src/tool/todowrite.txt +0 -167
- package/src/tool/tool.ts +0 -88
- package/src/tool/truncation.ts +0 -106
- package/src/tool/webfetch.ts +0 -182
- package/src/tool/webfetch.txt +0 -13
- package/src/tool/websearch.ts +0 -150
- package/src/tool/websearch.txt +0 -14
- package/src/tool/write.ts +0 -80
- package/src/tool/write.txt +0 -8
- package/src/util/archive.ts +0 -16
- package/src/util/color.ts +0 -19
- package/src/util/context.ts +0 -25
- package/src/util/defer.ts +0 -12
- package/src/util/eventloop.ts +0 -20
- package/src/util/filesystem.ts +0 -93
- package/src/util/fn.ts +0 -11
- package/src/util/format.ts +0 -20
- package/src/util/iife.ts +0 -3
- package/src/util/keybind.ts +0 -103
- package/src/util/lazy.ts +0 -18
- package/src/util/locale.ts +0 -81
- package/src/util/lock.ts +0 -98
- package/src/util/log.ts +0 -180
- package/src/util/queue.ts +0 -32
- package/src/util/rpc.ts +0 -66
- package/src/util/scrap.ts +0 -10
- package/src/util/signal.ts +0 -12
- package/src/util/timeout.ts +0 -14
- package/src/util/token.ts +0 -7
- package/src/util/wildcard.ts +0 -56
- package/src/worktree/index.ts +0 -217
- package/sst-env.d.ts +0 -9
- package/test/acp/event-subscription.test.ts +0 -436
- package/test/acp/system-prompt.test.ts +0 -262
- package/test/agent/agent.test.ts +0 -638
- package/test/bun.test.ts +0 -53
- package/test/cli/github-action.test.ts +0 -129
- package/test/cli/github-remote.test.ts +0 -80
- package/test/cli/tui/transcript.test.ts +0 -297
- package/test/config/agent-color.test.ts +0 -66
- package/test/config/config.test.ts +0 -1414
- package/test/config/fixtures/empty-frontmatter.md +0 -4
- package/test/config/fixtures/frontmatter.md +0 -28
- package/test/config/fixtures/no-frontmatter.md +0 -1
- package/test/config/markdown.test.ts +0 -192
- package/test/file/ignore.test.ts +0 -10
- package/test/file/path-traversal.test.ts +0 -198
- package/test/fixture/fixture.ts +0 -45
- package/test/fixture/lsp/fake-lsp-server.js +0 -77
- 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 -261
- package/test/patch/patch.test.ts +0 -348
- package/test/permission/arity.test.ts +0 -33
- package/test/permission/next.test.ts +0 -652
- package/test/permission-task.test.ts +0 -319
- package/test/plugin/codex.test.ts +0 -123
- package/test/preload.ts +0 -65
- package/test/project/project.test.ts +0 -120
- package/test/provider/amazon-bedrock.test.ts +0 -268
- package/test/provider/gitlab-duo.test.ts +0 -286
- package/test/provider/provider.test.ts +0 -2149
- package/test/provider/transform.test.ts +0 -1596
- package/test/question/question.test.ts +0 -300
- package/test/scheduler.test.ts +0 -73
- package/test/server/session-list.test.ts +0 -39
- package/test/server/session-select.test.ts +0 -78
- package/test/session/compaction.test.ts +0 -293
- package/test/session/llm.test.ts +0 -90
- package/test/session/message-v2.test.ts +0 -662
- package/test/session/retry.test.ts +0 -131
- package/test/session/revert-compact.test.ts +0 -285
- package/test/session/session.test.ts +0 -71
- package/test/skill/skill.test.ts +0 -185
- package/test/snapshot/snapshot.test.ts +0 -939
- package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
- package/test/tool/apply_patch.test.ts +0 -515
- package/test/tool/bash.test.ts +0 -320
- package/test/tool/external-directory.test.ts +0 -126
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +0 -33453
- package/test/tool/grep.test.ts +0 -109
- package/test/tool/question.test.ts +0 -105
- package/test/tool/read.test.ts +0 -332
- package/test/tool/registry.test.ts +0 -76
- package/test/tool/truncation.test.ts +0 -159
- package/test/util/filesystem.test.ts +0 -39
- package/test/util/format.test.ts +0 -59
- 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/timeout.test.ts +0 -21
- package/test/util/wildcard.test.ts +0 -75
- package/tsconfig.json +0 -16
package/src/acp/agent.ts
DELETED
|
@@ -1,1280 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RequestError,
|
|
3
|
-
type Agent as ACPAgent,
|
|
4
|
-
type AgentSideConnection,
|
|
5
|
-
type AuthenticateRequest,
|
|
6
|
-
type AuthMethod,
|
|
7
|
-
type CancelNotification,
|
|
8
|
-
type InitializeRequest,
|
|
9
|
-
type InitializeResponse,
|
|
10
|
-
type LoadSessionRequest,
|
|
11
|
-
type NewSessionRequest,
|
|
12
|
-
type PermissionOption,
|
|
13
|
-
type PlanEntry,
|
|
14
|
-
type PromptRequest,
|
|
15
|
-
type SetSessionModelRequest,
|
|
16
|
-
type SetSessionModeRequest,
|
|
17
|
-
type SetSessionModeResponse,
|
|
18
|
-
type ToolCallContent,
|
|
19
|
-
type ToolKind,
|
|
20
|
-
} from "@agentclientprotocol/sdk"
|
|
21
|
-
import { Log } from "../util/log"
|
|
22
|
-
import { ACPSessionManager } from "./session"
|
|
23
|
-
import type { ACPConfig } from "./types"
|
|
24
|
-
import { Provider } from "../provider/provider"
|
|
25
|
-
import { Agent as AgentModule } from "../agent/agent"
|
|
26
|
-
import { Installation } from "@/installation"
|
|
27
|
-
import { MessageV2 } from "@/session/message-v2"
|
|
28
|
-
import { Config } from "@/config/config"
|
|
29
|
-
import { Todo } from "@/session/todo"
|
|
30
|
-
import { z } from "zod"
|
|
31
|
-
import { LoadAPIKeyError } from "ai"
|
|
32
|
-
import type { Event, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
|
33
|
-
import { applyPatch } from "diff"
|
|
34
|
-
|
|
35
|
-
export namespace ACP {
|
|
36
|
-
const log = Log.create({ service: "acp-agent" })
|
|
37
|
-
|
|
38
|
-
export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) {
|
|
39
|
-
return {
|
|
40
|
-
create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
|
|
41
|
-
return new Agent(connection, fullConfig)
|
|
42
|
-
},
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export class Agent implements ACPAgent {
|
|
47
|
-
private connection: AgentSideConnection
|
|
48
|
-
private config: ACPConfig
|
|
49
|
-
private sdk: OpencodeClient
|
|
50
|
-
private sessionManager: ACPSessionManager
|
|
51
|
-
private eventAbort = new AbortController()
|
|
52
|
-
private eventStarted = false
|
|
53
|
-
private permissionQueues = new Map<string, Promise<void>>()
|
|
54
|
-
private permissionOptions: PermissionOption[] = [
|
|
55
|
-
{ optionId: "once", kind: "allow_once", name: "Allow once" },
|
|
56
|
-
{ optionId: "always", kind: "allow_always", name: "Always allow" },
|
|
57
|
-
{ optionId: "reject", kind: "reject_once", name: "Reject" },
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
constructor(connection: AgentSideConnection, config: ACPConfig) {
|
|
61
|
-
this.connection = connection
|
|
62
|
-
this.config = config
|
|
63
|
-
this.sdk = config.sdk
|
|
64
|
-
this.sessionManager = new ACPSessionManager(this.sdk)
|
|
65
|
-
this.startEventSubscription()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
private startEventSubscription() {
|
|
69
|
-
if (this.eventStarted) return
|
|
70
|
-
this.eventStarted = true
|
|
71
|
-
this.runEventSubscription().catch((error) => {
|
|
72
|
-
if (this.eventAbort.signal.aborted) return
|
|
73
|
-
log.error("event subscription failed", { error })
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private async runEventSubscription() {
|
|
78
|
-
while (true) {
|
|
79
|
-
if (this.eventAbort.signal.aborted) return
|
|
80
|
-
const events = await this.sdk.global.event({
|
|
81
|
-
signal: this.eventAbort.signal,
|
|
82
|
-
})
|
|
83
|
-
for await (const event of events.stream) {
|
|
84
|
-
if (this.eventAbort.signal.aborted) return
|
|
85
|
-
const payload = (event as any)?.payload
|
|
86
|
-
if (!payload) continue
|
|
87
|
-
await this.handleEvent(payload as Event).catch((error) => {
|
|
88
|
-
log.error("failed to handle event", { error, type: payload.type })
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private async handleEvent(event: Event) {
|
|
95
|
-
switch (event.type) {
|
|
96
|
-
case "permission.asked": {
|
|
97
|
-
const permission = event.properties
|
|
98
|
-
const session = this.sessionManager.tryGet(permission.sessionID)
|
|
99
|
-
if (!session) return
|
|
100
|
-
|
|
101
|
-
const prev = this.permissionQueues.get(permission.sessionID) ?? Promise.resolve()
|
|
102
|
-
const next = prev
|
|
103
|
-
.then(async () => {
|
|
104
|
-
const directory = session.cwd
|
|
105
|
-
|
|
106
|
-
const res = await this.connection
|
|
107
|
-
.requestPermission({
|
|
108
|
-
sessionId: permission.sessionID,
|
|
109
|
-
toolCall: {
|
|
110
|
-
toolCallId: permission.tool?.callID ?? permission.id,
|
|
111
|
-
status: "pending",
|
|
112
|
-
title: permission.permission,
|
|
113
|
-
rawInput: permission.metadata,
|
|
114
|
-
kind: toToolKind(permission.permission),
|
|
115
|
-
locations: toLocations(permission.permission, permission.metadata),
|
|
116
|
-
},
|
|
117
|
-
options: this.permissionOptions,
|
|
118
|
-
})
|
|
119
|
-
.catch(async (error) => {
|
|
120
|
-
log.error("failed to request permission from ACP", {
|
|
121
|
-
error,
|
|
122
|
-
permissionID: permission.id,
|
|
123
|
-
sessionID: permission.sessionID,
|
|
124
|
-
})
|
|
125
|
-
await this.sdk.permission.reply({
|
|
126
|
-
requestID: permission.id,
|
|
127
|
-
reply: "reject",
|
|
128
|
-
directory,
|
|
129
|
-
})
|
|
130
|
-
return undefined
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
if (!res) return
|
|
134
|
-
if (res.outcome.outcome !== "selected") {
|
|
135
|
-
await this.sdk.permission.reply({
|
|
136
|
-
requestID: permission.id,
|
|
137
|
-
reply: "reject",
|
|
138
|
-
directory,
|
|
139
|
-
})
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (res.outcome.optionId !== "reject" && permission.permission == "edit") {
|
|
144
|
-
const metadata = permission.metadata || {}
|
|
145
|
-
const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : ""
|
|
146
|
-
const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : ""
|
|
147
|
-
|
|
148
|
-
const content = await Bun.file(filepath).text()
|
|
149
|
-
const newContent = getNewContent(content, diff)
|
|
150
|
-
|
|
151
|
-
if (newContent) {
|
|
152
|
-
this.connection.writeTextFile({
|
|
153
|
-
sessionId: session.id,
|
|
154
|
-
path: filepath,
|
|
155
|
-
content: newContent,
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await this.sdk.permission.reply({
|
|
161
|
-
requestID: permission.id,
|
|
162
|
-
reply: res.outcome.optionId as "once" | "always" | "reject",
|
|
163
|
-
directory,
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
.catch((error) => {
|
|
167
|
-
log.error("failed to handle permission", { error, permissionID: permission.id })
|
|
168
|
-
})
|
|
169
|
-
.finally(() => {
|
|
170
|
-
if (this.permissionQueues.get(permission.sessionID) === next) {
|
|
171
|
-
this.permissionQueues.delete(permission.sessionID)
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
this.permissionQueues.set(permission.sessionID, next)
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
case "message.part.updated": {
|
|
179
|
-
log.info("message part updated", { event: event.properties })
|
|
180
|
-
const props = event.properties
|
|
181
|
-
const part = props.part
|
|
182
|
-
const session = this.sessionManager.tryGet(part.sessionID)
|
|
183
|
-
if (!session) return
|
|
184
|
-
const sessionId = session.id
|
|
185
|
-
const directory = session.cwd
|
|
186
|
-
|
|
187
|
-
const message = await this.sdk.session
|
|
188
|
-
.message(
|
|
189
|
-
{
|
|
190
|
-
sessionID: part.sessionID,
|
|
191
|
-
messageID: part.messageID,
|
|
192
|
-
directory,
|
|
193
|
-
},
|
|
194
|
-
{ throwOnError: true },
|
|
195
|
-
)
|
|
196
|
-
.then((x) => x.data)
|
|
197
|
-
.catch((error) => {
|
|
198
|
-
log.error("unexpected error when fetching message", { error })
|
|
199
|
-
return undefined
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
if (!message || message.info.role !== "assistant") return
|
|
203
|
-
|
|
204
|
-
if (part.type === "tool") {
|
|
205
|
-
switch (part.state.status) {
|
|
206
|
-
case "pending":
|
|
207
|
-
await this.connection
|
|
208
|
-
.sessionUpdate({
|
|
209
|
-
sessionId,
|
|
210
|
-
update: {
|
|
211
|
-
sessionUpdate: "tool_call",
|
|
212
|
-
toolCallId: part.callID,
|
|
213
|
-
title: part.tool,
|
|
214
|
-
kind: toToolKind(part.tool),
|
|
215
|
-
status: "pending",
|
|
216
|
-
locations: [],
|
|
217
|
-
rawInput: {},
|
|
218
|
-
},
|
|
219
|
-
})
|
|
220
|
-
.catch((error) => {
|
|
221
|
-
log.error("failed to send tool pending to ACP", { error })
|
|
222
|
-
})
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
case "running":
|
|
226
|
-
await this.connection
|
|
227
|
-
.sessionUpdate({
|
|
228
|
-
sessionId,
|
|
229
|
-
update: {
|
|
230
|
-
sessionUpdate: "tool_call_update",
|
|
231
|
-
toolCallId: part.callID,
|
|
232
|
-
status: "in_progress",
|
|
233
|
-
kind: toToolKind(part.tool),
|
|
234
|
-
title: part.tool,
|
|
235
|
-
locations: toLocations(part.tool, part.state.input),
|
|
236
|
-
rawInput: part.state.input,
|
|
237
|
-
},
|
|
238
|
-
})
|
|
239
|
-
.catch((error) => {
|
|
240
|
-
log.error("failed to send tool in_progress to ACP", { error })
|
|
241
|
-
})
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
case "completed": {
|
|
245
|
-
const kind = toToolKind(part.tool)
|
|
246
|
-
const content: ToolCallContent[] = [
|
|
247
|
-
{
|
|
248
|
-
type: "content",
|
|
249
|
-
content: {
|
|
250
|
-
type: "text",
|
|
251
|
-
text: part.state.output,
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
]
|
|
255
|
-
|
|
256
|
-
if (kind === "edit") {
|
|
257
|
-
const input = part.state.input
|
|
258
|
-
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
|
|
259
|
-
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
|
|
260
|
-
const newText =
|
|
261
|
-
typeof input["newString"] === "string"
|
|
262
|
-
? input["newString"]
|
|
263
|
-
: typeof input["content"] === "string"
|
|
264
|
-
? input["content"]
|
|
265
|
-
: ""
|
|
266
|
-
content.push({
|
|
267
|
-
type: "diff",
|
|
268
|
-
path: filePath,
|
|
269
|
-
oldText,
|
|
270
|
-
newText,
|
|
271
|
-
})
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (part.tool === "todowrite") {
|
|
275
|
-
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
|
|
276
|
-
if (parsedTodos.success) {
|
|
277
|
-
await this.connection
|
|
278
|
-
.sessionUpdate({
|
|
279
|
-
sessionId,
|
|
280
|
-
update: {
|
|
281
|
-
sessionUpdate: "plan",
|
|
282
|
-
entries: parsedTodos.data.map((todo) => {
|
|
283
|
-
const status: PlanEntry["status"] =
|
|
284
|
-
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
|
285
|
-
return {
|
|
286
|
-
priority: "medium",
|
|
287
|
-
status,
|
|
288
|
-
content: todo.content,
|
|
289
|
-
}
|
|
290
|
-
}),
|
|
291
|
-
},
|
|
292
|
-
})
|
|
293
|
-
.catch((error) => {
|
|
294
|
-
log.error("failed to send session update for todo", { error })
|
|
295
|
-
})
|
|
296
|
-
} else {
|
|
297
|
-
log.error("failed to parse todo output", { error: parsedTodos.error })
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
await this.connection
|
|
302
|
-
.sessionUpdate({
|
|
303
|
-
sessionId,
|
|
304
|
-
update: {
|
|
305
|
-
sessionUpdate: "tool_call_update",
|
|
306
|
-
toolCallId: part.callID,
|
|
307
|
-
status: "completed",
|
|
308
|
-
kind,
|
|
309
|
-
content,
|
|
310
|
-
title: part.state.title,
|
|
311
|
-
rawInput: part.state.input,
|
|
312
|
-
rawOutput: {
|
|
313
|
-
output: part.state.output,
|
|
314
|
-
metadata: part.state.metadata,
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
})
|
|
318
|
-
.catch((error) => {
|
|
319
|
-
log.error("failed to send tool completed to ACP", { error })
|
|
320
|
-
})
|
|
321
|
-
return
|
|
322
|
-
}
|
|
323
|
-
case "error":
|
|
324
|
-
await this.connection
|
|
325
|
-
.sessionUpdate({
|
|
326
|
-
sessionId,
|
|
327
|
-
update: {
|
|
328
|
-
sessionUpdate: "tool_call_update",
|
|
329
|
-
toolCallId: part.callID,
|
|
330
|
-
status: "failed",
|
|
331
|
-
kind: toToolKind(part.tool),
|
|
332
|
-
title: part.tool,
|
|
333
|
-
rawInput: part.state.input,
|
|
334
|
-
content: [
|
|
335
|
-
{
|
|
336
|
-
type: "content",
|
|
337
|
-
content: {
|
|
338
|
-
type: "text",
|
|
339
|
-
text: part.state.error,
|
|
340
|
-
},
|
|
341
|
-
},
|
|
342
|
-
],
|
|
343
|
-
rawOutput: {
|
|
344
|
-
error: part.state.error,
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
})
|
|
348
|
-
.catch((error) => {
|
|
349
|
-
log.error("failed to send tool error to ACP", { error })
|
|
350
|
-
})
|
|
351
|
-
return
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (part.type === "text") {
|
|
356
|
-
const delta = props.delta
|
|
357
|
-
if (delta && part.ignored !== true) {
|
|
358
|
-
await this.connection
|
|
359
|
-
.sessionUpdate({
|
|
360
|
-
sessionId,
|
|
361
|
-
update: {
|
|
362
|
-
sessionUpdate: "agent_message_chunk",
|
|
363
|
-
content: {
|
|
364
|
-
type: "text",
|
|
365
|
-
text: delta,
|
|
366
|
-
},
|
|
367
|
-
},
|
|
368
|
-
})
|
|
369
|
-
.catch((error) => {
|
|
370
|
-
log.error("failed to send text to ACP", { error })
|
|
371
|
-
})
|
|
372
|
-
}
|
|
373
|
-
return
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (part.type === "reasoning") {
|
|
377
|
-
const delta = props.delta
|
|
378
|
-
if (delta) {
|
|
379
|
-
await this.connection
|
|
380
|
-
.sessionUpdate({
|
|
381
|
-
sessionId,
|
|
382
|
-
update: {
|
|
383
|
-
sessionUpdate: "agent_thought_chunk",
|
|
384
|
-
content: {
|
|
385
|
-
type: "text",
|
|
386
|
-
text: delta,
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
})
|
|
390
|
-
.catch((error) => {
|
|
391
|
-
log.error("failed to send reasoning to ACP", { error })
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async initialize(params: InitializeRequest): Promise<InitializeResponse> {
|
|
401
|
-
log.info("initialize", { protocolVersion: params.protocolVersion })
|
|
402
|
-
|
|
403
|
-
const authMethod: AuthMethod = {
|
|
404
|
-
description: "Run `nuwaxcode auth login` in the terminal",
|
|
405
|
-
name: "Login with nuwaxcode",
|
|
406
|
-
id: "nuwaxcode-login",
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// If client supports terminal-auth capability, use that instead.
|
|
410
|
-
if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
|
|
411
|
-
authMethod._meta = {
|
|
412
|
-
"terminal-auth": {
|
|
413
|
-
command: "nuwaxcode",
|
|
414
|
-
args: ["auth", "login"],
|
|
415
|
-
label: "NuwaxCode Login",
|
|
416
|
-
},
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
protocolVersion: 1,
|
|
422
|
-
agentCapabilities: {
|
|
423
|
-
loadSession: true,
|
|
424
|
-
mcpCapabilities: {
|
|
425
|
-
http: true,
|
|
426
|
-
sse: true,
|
|
427
|
-
},
|
|
428
|
-
promptCapabilities: {
|
|
429
|
-
embeddedContext: true,
|
|
430
|
-
image: true,
|
|
431
|
-
},
|
|
432
|
-
},
|
|
433
|
-
authMethods: [authMethod],
|
|
434
|
-
agentInfo: {
|
|
435
|
-
name: "OpenCode",
|
|
436
|
-
version: Installation.VERSION,
|
|
437
|
-
},
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async authenticate(_params: AuthenticateRequest) {
|
|
442
|
-
throw new Error("Authentication not implemented")
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
async newSession(params: NewSessionRequest) {
|
|
446
|
-
const directory = params.cwd
|
|
447
|
-
try {
|
|
448
|
-
const model = await defaultModel(this.config, directory)
|
|
449
|
-
|
|
450
|
-
// Extract systemPrompt from ACP meta (similar to claude-code-acp pattern)
|
|
451
|
-
const systemPrompt = (params._meta as { systemPrompt?: string | { append: string } } | undefined)?.systemPrompt
|
|
452
|
-
|
|
453
|
-
// Store ACP session state
|
|
454
|
-
const state = await this.sessionManager.create(params.cwd, params.mcpServers, model, systemPrompt)
|
|
455
|
-
const sessionId = state.id
|
|
456
|
-
|
|
457
|
-
log.info("creating_session", { sessionId, mcpServers: params.mcpServers.length, hasSystemPrompt: !!systemPrompt })
|
|
458
|
-
|
|
459
|
-
const load = await this.loadSessionMode({
|
|
460
|
-
cwd: directory,
|
|
461
|
-
mcpServers: params.mcpServers,
|
|
462
|
-
sessionId,
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
return {
|
|
466
|
-
sessionId,
|
|
467
|
-
models: load.models,
|
|
468
|
-
modes: load.modes,
|
|
469
|
-
_meta: {},
|
|
470
|
-
}
|
|
471
|
-
} catch (e) {
|
|
472
|
-
const error = MessageV2.fromError(e, {
|
|
473
|
-
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
|
474
|
-
})
|
|
475
|
-
if (LoadAPIKeyError.isInstance(error)) {
|
|
476
|
-
throw RequestError.authRequired()
|
|
477
|
-
}
|
|
478
|
-
throw e
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async loadSession(params: LoadSessionRequest) {
|
|
483
|
-
const directory = params.cwd
|
|
484
|
-
const sessionId = params.sessionId
|
|
485
|
-
|
|
486
|
-
try {
|
|
487
|
-
const model = await defaultModel(this.config, directory)
|
|
488
|
-
|
|
489
|
-
// Store ACP session state
|
|
490
|
-
await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model)
|
|
491
|
-
|
|
492
|
-
log.info("load_session", { sessionId, mcpServers: params.mcpServers.length })
|
|
493
|
-
|
|
494
|
-
const result = await this.loadSessionMode({
|
|
495
|
-
cwd: directory,
|
|
496
|
-
mcpServers: params.mcpServers,
|
|
497
|
-
sessionId,
|
|
498
|
-
})
|
|
499
|
-
|
|
500
|
-
// Replay session history
|
|
501
|
-
const messages = await this.sdk.session
|
|
502
|
-
.messages(
|
|
503
|
-
{
|
|
504
|
-
sessionID: sessionId,
|
|
505
|
-
directory,
|
|
506
|
-
},
|
|
507
|
-
{ throwOnError: true },
|
|
508
|
-
)
|
|
509
|
-
.then((x) => x.data)
|
|
510
|
-
.catch((err) => {
|
|
511
|
-
log.error("unexpected error when fetching message", { error: err })
|
|
512
|
-
return undefined
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
const lastUser = messages?.findLast((m) => m.info.role === "user")?.info
|
|
516
|
-
if (lastUser?.role === "user") {
|
|
517
|
-
result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`
|
|
518
|
-
if (result.modes.availableModes.some((m) => m.id === lastUser.agent)) {
|
|
519
|
-
result.modes.currentModeId = lastUser.agent
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
for (const msg of messages ?? []) {
|
|
524
|
-
log.debug("replay message", msg)
|
|
525
|
-
await this.processMessage(msg)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return result
|
|
529
|
-
} catch (e) {
|
|
530
|
-
const error = MessageV2.fromError(e, {
|
|
531
|
-
providerID: this.config.defaultModel?.providerID ?? "unknown",
|
|
532
|
-
})
|
|
533
|
-
if (LoadAPIKeyError.isInstance(error)) {
|
|
534
|
-
throw RequestError.authRequired()
|
|
535
|
-
}
|
|
536
|
-
throw e
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
private async processMessage(message: SessionMessageResponse) {
|
|
541
|
-
log.debug("process message", message)
|
|
542
|
-
if (message.info.role !== "assistant" && message.info.role !== "user") return
|
|
543
|
-
const sessionId = message.info.sessionID
|
|
544
|
-
|
|
545
|
-
for (const part of message.parts) {
|
|
546
|
-
if (part.type === "tool") {
|
|
547
|
-
switch (part.state.status) {
|
|
548
|
-
case "pending":
|
|
549
|
-
await this.connection
|
|
550
|
-
.sessionUpdate({
|
|
551
|
-
sessionId,
|
|
552
|
-
update: {
|
|
553
|
-
sessionUpdate: "tool_call",
|
|
554
|
-
toolCallId: part.callID,
|
|
555
|
-
title: part.tool,
|
|
556
|
-
kind: toToolKind(part.tool),
|
|
557
|
-
status: "pending",
|
|
558
|
-
locations: [],
|
|
559
|
-
rawInput: {},
|
|
560
|
-
},
|
|
561
|
-
})
|
|
562
|
-
.catch((err) => {
|
|
563
|
-
log.error("failed to send tool pending to ACP", { error: err })
|
|
564
|
-
})
|
|
565
|
-
break
|
|
566
|
-
case "running":
|
|
567
|
-
await this.connection
|
|
568
|
-
.sessionUpdate({
|
|
569
|
-
sessionId,
|
|
570
|
-
update: {
|
|
571
|
-
sessionUpdate: "tool_call_update",
|
|
572
|
-
toolCallId: part.callID,
|
|
573
|
-
status: "in_progress",
|
|
574
|
-
kind: toToolKind(part.tool),
|
|
575
|
-
title: part.tool,
|
|
576
|
-
locations: toLocations(part.tool, part.state.input),
|
|
577
|
-
rawInput: part.state.input,
|
|
578
|
-
},
|
|
579
|
-
})
|
|
580
|
-
.catch((err) => {
|
|
581
|
-
log.error("failed to send tool in_progress to ACP", { error: err })
|
|
582
|
-
})
|
|
583
|
-
break
|
|
584
|
-
case "completed":
|
|
585
|
-
const kind = toToolKind(part.tool)
|
|
586
|
-
const content: ToolCallContent[] = [
|
|
587
|
-
{
|
|
588
|
-
type: "content",
|
|
589
|
-
content: {
|
|
590
|
-
type: "text",
|
|
591
|
-
text: part.state.output,
|
|
592
|
-
},
|
|
593
|
-
},
|
|
594
|
-
]
|
|
595
|
-
|
|
596
|
-
if (kind === "edit") {
|
|
597
|
-
const input = part.state.input
|
|
598
|
-
const filePath = typeof input["filePath"] === "string" ? input["filePath"] : ""
|
|
599
|
-
const oldText = typeof input["oldString"] === "string" ? input["oldString"] : ""
|
|
600
|
-
const newText =
|
|
601
|
-
typeof input["newString"] === "string"
|
|
602
|
-
? input["newString"]
|
|
603
|
-
: typeof input["content"] === "string"
|
|
604
|
-
? input["content"]
|
|
605
|
-
: ""
|
|
606
|
-
content.push({
|
|
607
|
-
type: "diff",
|
|
608
|
-
path: filePath,
|
|
609
|
-
oldText,
|
|
610
|
-
newText,
|
|
611
|
-
})
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
if (part.tool === "todowrite") {
|
|
615
|
-
const parsedTodos = z.array(Todo.Info).safeParse(JSON.parse(part.state.output))
|
|
616
|
-
if (parsedTodos.success) {
|
|
617
|
-
await this.connection
|
|
618
|
-
.sessionUpdate({
|
|
619
|
-
sessionId,
|
|
620
|
-
update: {
|
|
621
|
-
sessionUpdate: "plan",
|
|
622
|
-
entries: parsedTodos.data.map((todo) => {
|
|
623
|
-
const status: PlanEntry["status"] =
|
|
624
|
-
todo.status === "cancelled" ? "completed" : (todo.status as PlanEntry["status"])
|
|
625
|
-
return {
|
|
626
|
-
priority: "medium",
|
|
627
|
-
status,
|
|
628
|
-
content: todo.content,
|
|
629
|
-
}
|
|
630
|
-
}),
|
|
631
|
-
},
|
|
632
|
-
})
|
|
633
|
-
.catch((err) => {
|
|
634
|
-
log.error("failed to send session update for todo", { error: err })
|
|
635
|
-
})
|
|
636
|
-
} else {
|
|
637
|
-
log.error("failed to parse todo output", { error: parsedTodos.error })
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
await this.connection
|
|
642
|
-
.sessionUpdate({
|
|
643
|
-
sessionId,
|
|
644
|
-
update: {
|
|
645
|
-
sessionUpdate: "tool_call_update",
|
|
646
|
-
toolCallId: part.callID,
|
|
647
|
-
status: "completed",
|
|
648
|
-
kind,
|
|
649
|
-
content,
|
|
650
|
-
title: part.state.title,
|
|
651
|
-
rawInput: part.state.input,
|
|
652
|
-
rawOutput: {
|
|
653
|
-
output: part.state.output,
|
|
654
|
-
metadata: part.state.metadata,
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
})
|
|
658
|
-
.catch((err) => {
|
|
659
|
-
log.error("failed to send tool completed to ACP", { error: err })
|
|
660
|
-
})
|
|
661
|
-
break
|
|
662
|
-
case "error":
|
|
663
|
-
await this.connection
|
|
664
|
-
.sessionUpdate({
|
|
665
|
-
sessionId,
|
|
666
|
-
update: {
|
|
667
|
-
sessionUpdate: "tool_call_update",
|
|
668
|
-
toolCallId: part.callID,
|
|
669
|
-
status: "failed",
|
|
670
|
-
kind: toToolKind(part.tool),
|
|
671
|
-
title: part.tool,
|
|
672
|
-
rawInput: part.state.input,
|
|
673
|
-
content: [
|
|
674
|
-
{
|
|
675
|
-
type: "content",
|
|
676
|
-
content: {
|
|
677
|
-
type: "text",
|
|
678
|
-
text: part.state.error,
|
|
679
|
-
},
|
|
680
|
-
},
|
|
681
|
-
],
|
|
682
|
-
rawOutput: {
|
|
683
|
-
error: part.state.error,
|
|
684
|
-
},
|
|
685
|
-
},
|
|
686
|
-
})
|
|
687
|
-
.catch((err) => {
|
|
688
|
-
log.error("failed to send tool error to ACP", { error: err })
|
|
689
|
-
})
|
|
690
|
-
break
|
|
691
|
-
}
|
|
692
|
-
} else if (part.type === "text") {
|
|
693
|
-
if (part.text && !part.ignored) {
|
|
694
|
-
await this.connection
|
|
695
|
-
.sessionUpdate({
|
|
696
|
-
sessionId,
|
|
697
|
-
update: {
|
|
698
|
-
sessionUpdate: message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk",
|
|
699
|
-
content: {
|
|
700
|
-
type: "text",
|
|
701
|
-
text: part.text,
|
|
702
|
-
},
|
|
703
|
-
},
|
|
704
|
-
})
|
|
705
|
-
.catch((err) => {
|
|
706
|
-
log.error("failed to send text to ACP", { error: err })
|
|
707
|
-
})
|
|
708
|
-
}
|
|
709
|
-
} else if (part.type === "file") {
|
|
710
|
-
// Replay file attachments as appropriate ACP content blocks.
|
|
711
|
-
// OpenCode stores files internally as { type: "file", url, filename, mime }.
|
|
712
|
-
// We convert these back to ACP blocks based on the URL scheme and MIME type:
|
|
713
|
-
// - file:// URLs → resource_link
|
|
714
|
-
// - data: URLs with image/* → image block
|
|
715
|
-
// - data: URLs with text/* or application/json → resource with text
|
|
716
|
-
// - data: URLs with other types → resource with blob
|
|
717
|
-
const url = part.url
|
|
718
|
-
const filename = part.filename ?? "file"
|
|
719
|
-
const mime = part.mime || "application/octet-stream"
|
|
720
|
-
const messageChunk = message.info.role === "user" ? "user_message_chunk" : "agent_message_chunk"
|
|
721
|
-
|
|
722
|
-
if (url.startsWith("file://")) {
|
|
723
|
-
// Local file reference - send as resource_link
|
|
724
|
-
await this.connection
|
|
725
|
-
.sessionUpdate({
|
|
726
|
-
sessionId,
|
|
727
|
-
update: {
|
|
728
|
-
sessionUpdate: messageChunk,
|
|
729
|
-
content: { type: "resource_link", uri: url, name: filename, mimeType: mime },
|
|
730
|
-
},
|
|
731
|
-
})
|
|
732
|
-
.catch((err) => {
|
|
733
|
-
log.error("failed to send resource_link to ACP", { error: err })
|
|
734
|
-
})
|
|
735
|
-
} else if (url.startsWith("data:")) {
|
|
736
|
-
// Embedded content - parse data URL and send as appropriate block type
|
|
737
|
-
const base64Match = url.match(/^data:([^;]+);base64,(.*)$/)
|
|
738
|
-
const dataMime = base64Match?.[1]
|
|
739
|
-
const base64Data = base64Match?.[2] ?? ""
|
|
740
|
-
|
|
741
|
-
const effectiveMime = dataMime || mime
|
|
742
|
-
|
|
743
|
-
if (effectiveMime.startsWith("image/")) {
|
|
744
|
-
// Image - send as image block
|
|
745
|
-
await this.connection
|
|
746
|
-
.sessionUpdate({
|
|
747
|
-
sessionId,
|
|
748
|
-
update: {
|
|
749
|
-
sessionUpdate: messageChunk,
|
|
750
|
-
content: {
|
|
751
|
-
type: "image",
|
|
752
|
-
mimeType: effectiveMime,
|
|
753
|
-
data: base64Data,
|
|
754
|
-
uri: `file://${filename}`,
|
|
755
|
-
},
|
|
756
|
-
},
|
|
757
|
-
})
|
|
758
|
-
.catch((err) => {
|
|
759
|
-
log.error("failed to send image to ACP", { error: err })
|
|
760
|
-
})
|
|
761
|
-
} else {
|
|
762
|
-
// Non-image: text types get decoded, binary types stay as blob
|
|
763
|
-
const isText = effectiveMime.startsWith("text/") || effectiveMime === "application/json"
|
|
764
|
-
const resource = isText
|
|
765
|
-
? {
|
|
766
|
-
uri: `file://${filename}`,
|
|
767
|
-
mimeType: effectiveMime,
|
|
768
|
-
text: Buffer.from(base64Data, "base64").toString("utf-8"),
|
|
769
|
-
}
|
|
770
|
-
: { uri: `file://${filename}`, mimeType: effectiveMime, blob: base64Data }
|
|
771
|
-
|
|
772
|
-
await this.connection
|
|
773
|
-
.sessionUpdate({
|
|
774
|
-
sessionId,
|
|
775
|
-
update: {
|
|
776
|
-
sessionUpdate: messageChunk,
|
|
777
|
-
content: { type: "resource", resource },
|
|
778
|
-
},
|
|
779
|
-
})
|
|
780
|
-
.catch((err) => {
|
|
781
|
-
log.error("failed to send resource to ACP", { error: err })
|
|
782
|
-
})
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
// URLs that don't match file:// or data: are skipped (unsupported)
|
|
786
|
-
} else if (part.type === "reasoning") {
|
|
787
|
-
if (part.text) {
|
|
788
|
-
await this.connection
|
|
789
|
-
.sessionUpdate({
|
|
790
|
-
sessionId,
|
|
791
|
-
update: {
|
|
792
|
-
sessionUpdate: "agent_thought_chunk",
|
|
793
|
-
content: {
|
|
794
|
-
type: "text",
|
|
795
|
-
text: part.text,
|
|
796
|
-
},
|
|
797
|
-
},
|
|
798
|
-
})
|
|
799
|
-
.catch((err) => {
|
|
800
|
-
log.error("failed to send reasoning to ACP", { error: err })
|
|
801
|
-
})
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
private async loadSessionMode(params: LoadSessionRequest) {
|
|
808
|
-
const directory = params.cwd
|
|
809
|
-
const model = await defaultModel(this.config, directory)
|
|
810
|
-
const sessionId = params.sessionId
|
|
811
|
-
|
|
812
|
-
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
|
813
|
-
const entries = providers.sort((a, b) => {
|
|
814
|
-
const nameA = a.name.toLowerCase()
|
|
815
|
-
const nameB = b.name.toLowerCase()
|
|
816
|
-
if (nameA < nameB) return -1
|
|
817
|
-
if (nameA > nameB) return 1
|
|
818
|
-
return 0
|
|
819
|
-
})
|
|
820
|
-
const availableModels = entries.flatMap((provider) => {
|
|
821
|
-
const models = Provider.sort(Object.values(provider.models))
|
|
822
|
-
return models.map((model) => ({
|
|
823
|
-
modelId: `${provider.id}/${model.id}`,
|
|
824
|
-
name: `${provider.name}/${model.name}`,
|
|
825
|
-
}))
|
|
826
|
-
})
|
|
827
|
-
|
|
828
|
-
const agents = await this.config.sdk.app
|
|
829
|
-
.agents(
|
|
830
|
-
{
|
|
831
|
-
directory,
|
|
832
|
-
},
|
|
833
|
-
{ throwOnError: true },
|
|
834
|
-
)
|
|
835
|
-
.then((resp) => resp.data!)
|
|
836
|
-
|
|
837
|
-
const commands = await this.config.sdk.command
|
|
838
|
-
.list(
|
|
839
|
-
{
|
|
840
|
-
directory,
|
|
841
|
-
},
|
|
842
|
-
{ throwOnError: true },
|
|
843
|
-
)
|
|
844
|
-
.then((resp) => resp.data!)
|
|
845
|
-
|
|
846
|
-
const availableCommands = commands.map((command) => ({
|
|
847
|
-
name: command.name,
|
|
848
|
-
description: command.description ?? "",
|
|
849
|
-
}))
|
|
850
|
-
const names = new Set(availableCommands.map((c) => c.name))
|
|
851
|
-
if (!names.has("compact"))
|
|
852
|
-
availableCommands.push({
|
|
853
|
-
name: "compact",
|
|
854
|
-
description: "compact the session",
|
|
855
|
-
})
|
|
856
|
-
|
|
857
|
-
const availableModes = agents
|
|
858
|
-
.filter((agent) => agent.mode !== "subagent" && !agent.hidden)
|
|
859
|
-
.map((agent) => ({
|
|
860
|
-
id: agent.name,
|
|
861
|
-
name: agent.name,
|
|
862
|
-
description: agent.description,
|
|
863
|
-
}))
|
|
864
|
-
|
|
865
|
-
const defaultAgentName = await AgentModule.defaultAgent()
|
|
866
|
-
const currentModeId = availableModes.find((m) => m.name === defaultAgentName)?.id ?? availableModes[0].id
|
|
867
|
-
|
|
868
|
-
// Persist the default mode so prompt() uses it immediately
|
|
869
|
-
this.sessionManager.setMode(sessionId, currentModeId)
|
|
870
|
-
|
|
871
|
-
const mcpServers: Record<string, Config.Mcp> = {}
|
|
872
|
-
for (const server of params.mcpServers) {
|
|
873
|
-
if ("type" in server) {
|
|
874
|
-
mcpServers[server.name] = {
|
|
875
|
-
url: server.url,
|
|
876
|
-
headers: server.headers.reduce<Record<string, string>>((acc, { name, value }) => {
|
|
877
|
-
acc[name] = value
|
|
878
|
-
return acc
|
|
879
|
-
}, {}),
|
|
880
|
-
type: "remote",
|
|
881
|
-
}
|
|
882
|
-
} else {
|
|
883
|
-
mcpServers[server.name] = {
|
|
884
|
-
type: "local",
|
|
885
|
-
command: [server.command, ...server.args],
|
|
886
|
-
environment: server.env.reduce<Record<string, string>>((acc, { name, value }) => {
|
|
887
|
-
acc[name] = value
|
|
888
|
-
return acc
|
|
889
|
-
}, {}),
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
await Promise.all(
|
|
895
|
-
Object.entries(mcpServers).map(async ([key, mcp]) => {
|
|
896
|
-
await this.sdk.mcp
|
|
897
|
-
.add(
|
|
898
|
-
{
|
|
899
|
-
directory,
|
|
900
|
-
name: key,
|
|
901
|
-
config: mcp,
|
|
902
|
-
},
|
|
903
|
-
{ throwOnError: true },
|
|
904
|
-
)
|
|
905
|
-
.catch((error) => {
|
|
906
|
-
log.error("failed to add mcp server", { name: key, error })
|
|
907
|
-
})
|
|
908
|
-
}),
|
|
909
|
-
)
|
|
910
|
-
|
|
911
|
-
setTimeout(() => {
|
|
912
|
-
this.connection.sessionUpdate({
|
|
913
|
-
sessionId,
|
|
914
|
-
update: {
|
|
915
|
-
sessionUpdate: "available_commands_update",
|
|
916
|
-
availableCommands,
|
|
917
|
-
},
|
|
918
|
-
})
|
|
919
|
-
}, 0)
|
|
920
|
-
|
|
921
|
-
return {
|
|
922
|
-
sessionId,
|
|
923
|
-
models: {
|
|
924
|
-
currentModelId: `${model.providerID}/${model.modelID}`,
|
|
925
|
-
availableModels,
|
|
926
|
-
},
|
|
927
|
-
modes: {
|
|
928
|
-
availableModes,
|
|
929
|
-
currentModeId,
|
|
930
|
-
},
|
|
931
|
-
_meta: {},
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
async setSessionModel(params: SetSessionModelRequest) {
|
|
936
|
-
const session = this.sessionManager.get(params.sessionId)
|
|
937
|
-
|
|
938
|
-
const model = Provider.parseModel(params.modelId)
|
|
939
|
-
|
|
940
|
-
this.sessionManager.setModel(session.id, {
|
|
941
|
-
providerID: model.providerID,
|
|
942
|
-
modelID: model.modelID,
|
|
943
|
-
})
|
|
944
|
-
|
|
945
|
-
return {
|
|
946
|
-
_meta: {},
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse | void> {
|
|
951
|
-
this.sessionManager.get(params.sessionId)
|
|
952
|
-
await this.config.sdk.app
|
|
953
|
-
.agents({}, { throwOnError: true })
|
|
954
|
-
.then((x) => x.data)
|
|
955
|
-
.then((agent) => {
|
|
956
|
-
if (!agent) throw new Error(`Agent not found: ${params.modeId}`)
|
|
957
|
-
})
|
|
958
|
-
this.sessionManager.setMode(params.sessionId, params.modeId)
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
async prompt(params: PromptRequest) {
|
|
962
|
-
const sessionID = params.sessionId
|
|
963
|
-
const session = this.sessionManager.get(sessionID)
|
|
964
|
-
const directory = session.cwd
|
|
965
|
-
|
|
966
|
-
const current = session.model
|
|
967
|
-
const model = current ?? (await defaultModel(this.config, directory))
|
|
968
|
-
if (!current) {
|
|
969
|
-
this.sessionManager.setModel(session.id, model)
|
|
970
|
-
}
|
|
971
|
-
const agent = session.modeId ?? (await AgentModule.defaultAgent())
|
|
972
|
-
|
|
973
|
-
const parts: Array<
|
|
974
|
-
{ type: "text"; text: string } | { type: "file"; url: string; filename: string; mime: string }
|
|
975
|
-
> = []
|
|
976
|
-
for (const part of params.prompt) {
|
|
977
|
-
switch (part.type) {
|
|
978
|
-
case "text":
|
|
979
|
-
parts.push({
|
|
980
|
-
type: "text" as const,
|
|
981
|
-
text: part.text,
|
|
982
|
-
})
|
|
983
|
-
break
|
|
984
|
-
case "image": {
|
|
985
|
-
const parsed = parseUri(part.uri ?? "")
|
|
986
|
-
const filename = parsed.type === "file" ? parsed.filename : "image"
|
|
987
|
-
if (part.data) {
|
|
988
|
-
parts.push({
|
|
989
|
-
type: "file",
|
|
990
|
-
url: `data:${part.mimeType};base64,${part.data}`,
|
|
991
|
-
filename,
|
|
992
|
-
mime: part.mimeType,
|
|
993
|
-
})
|
|
994
|
-
} else if (part.uri && part.uri.startsWith("http:")) {
|
|
995
|
-
parts.push({
|
|
996
|
-
type: "file",
|
|
997
|
-
url: part.uri,
|
|
998
|
-
filename,
|
|
999
|
-
mime: part.mimeType,
|
|
1000
|
-
})
|
|
1001
|
-
}
|
|
1002
|
-
break
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
case "resource_link":
|
|
1006
|
-
const parsed = parseUri(part.uri)
|
|
1007
|
-
// Use the name from resource_link if available
|
|
1008
|
-
if (part.name && parsed.type === "file") {
|
|
1009
|
-
parsed.filename = part.name
|
|
1010
|
-
}
|
|
1011
|
-
parts.push(parsed)
|
|
1012
|
-
|
|
1013
|
-
break
|
|
1014
|
-
|
|
1015
|
-
case "resource": {
|
|
1016
|
-
const resource = part.resource
|
|
1017
|
-
if ("text" in resource && resource.text) {
|
|
1018
|
-
parts.push({
|
|
1019
|
-
type: "text",
|
|
1020
|
-
text: resource.text,
|
|
1021
|
-
})
|
|
1022
|
-
} else if ("blob" in resource && resource.blob && resource.mimeType) {
|
|
1023
|
-
// Binary resource (PDFs, etc.): store as file part with data URL
|
|
1024
|
-
const parsed = parseUri(resource.uri ?? "")
|
|
1025
|
-
const filename = parsed.type === "file" ? parsed.filename : "file"
|
|
1026
|
-
parts.push({
|
|
1027
|
-
type: "file",
|
|
1028
|
-
url: `data:${resource.mimeType};base64,${resource.blob}`,
|
|
1029
|
-
filename,
|
|
1030
|
-
mime: resource.mimeType,
|
|
1031
|
-
})
|
|
1032
|
-
}
|
|
1033
|
-
break
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
default:
|
|
1037
|
-
break
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
log.info("parts", { parts })
|
|
1042
|
-
|
|
1043
|
-
const cmd = (() => {
|
|
1044
|
-
const text = parts
|
|
1045
|
-
.filter((p): p is { type: "text"; text: string } => p.type === "text")
|
|
1046
|
-
.map((p) => p.text)
|
|
1047
|
-
.join("")
|
|
1048
|
-
.trim()
|
|
1049
|
-
|
|
1050
|
-
if (!text.startsWith("/")) return
|
|
1051
|
-
|
|
1052
|
-
const [name, ...rest] = text.slice(1).split(/\s+/)
|
|
1053
|
-
return { name, args: rest.join(" ").trim() }
|
|
1054
|
-
})()
|
|
1055
|
-
|
|
1056
|
-
const done = {
|
|
1057
|
-
stopReason: "end_turn" as const,
|
|
1058
|
-
_meta: {},
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
if (!cmd) {
|
|
1062
|
-
await this.sdk.session.prompt({
|
|
1063
|
-
sessionID,
|
|
1064
|
-
model: {
|
|
1065
|
-
providerID: model.providerID,
|
|
1066
|
-
modelID: model.modelID,
|
|
1067
|
-
},
|
|
1068
|
-
system: typeof session.systemPrompt === "string" ? session.systemPrompt : session.systemPrompt?.append,
|
|
1069
|
-
parts,
|
|
1070
|
-
agent,
|
|
1071
|
-
directory,
|
|
1072
|
-
})
|
|
1073
|
-
return done
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
const command = await this.config.sdk.command
|
|
1077
|
-
.list({ directory }, { throwOnError: true })
|
|
1078
|
-
.then((x) => x.data!.find((c) => c.name === cmd.name))
|
|
1079
|
-
if (command) {
|
|
1080
|
-
await this.sdk.session.command({
|
|
1081
|
-
sessionID,
|
|
1082
|
-
command: command.name,
|
|
1083
|
-
arguments: cmd.args,
|
|
1084
|
-
model: model.providerID + "/" + model.modelID,
|
|
1085
|
-
agent,
|
|
1086
|
-
directory,
|
|
1087
|
-
})
|
|
1088
|
-
return done
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
switch (cmd.name) {
|
|
1092
|
-
case "compact":
|
|
1093
|
-
await this.config.sdk.session.summarize(
|
|
1094
|
-
{
|
|
1095
|
-
sessionID,
|
|
1096
|
-
directory,
|
|
1097
|
-
providerID: model.providerID,
|
|
1098
|
-
modelID: model.modelID,
|
|
1099
|
-
},
|
|
1100
|
-
{ throwOnError: true },
|
|
1101
|
-
)
|
|
1102
|
-
break
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
return done
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
async cancel(params: CancelNotification) {
|
|
1109
|
-
const session = this.sessionManager.get(params.sessionId)
|
|
1110
|
-
await this.config.sdk.session.abort(
|
|
1111
|
-
{
|
|
1112
|
-
sessionID: params.sessionId,
|
|
1113
|
-
directory: session.cwd,
|
|
1114
|
-
},
|
|
1115
|
-
{ throwOnError: true },
|
|
1116
|
-
)
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
function toToolKind(toolName: string): ToolKind {
|
|
1121
|
-
const tool = toolName.toLocaleLowerCase()
|
|
1122
|
-
switch (tool) {
|
|
1123
|
-
case "bash":
|
|
1124
|
-
return "execute"
|
|
1125
|
-
case "webfetch":
|
|
1126
|
-
return "fetch"
|
|
1127
|
-
|
|
1128
|
-
case "edit":
|
|
1129
|
-
case "patch":
|
|
1130
|
-
case "write":
|
|
1131
|
-
return "edit"
|
|
1132
|
-
|
|
1133
|
-
case "grep":
|
|
1134
|
-
case "glob":
|
|
1135
|
-
case "context7_resolve_library_id":
|
|
1136
|
-
case "context7_get_library_docs":
|
|
1137
|
-
return "search"
|
|
1138
|
-
|
|
1139
|
-
case "list":
|
|
1140
|
-
case "read":
|
|
1141
|
-
return "read"
|
|
1142
|
-
|
|
1143
|
-
default:
|
|
1144
|
-
return "other"
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
|
|
1149
|
-
const tool = toolName.toLocaleLowerCase()
|
|
1150
|
-
switch (tool) {
|
|
1151
|
-
case "read":
|
|
1152
|
-
case "edit":
|
|
1153
|
-
case "write":
|
|
1154
|
-
return input["filePath"] ? [{ path: input["filePath"] }] : []
|
|
1155
|
-
case "glob":
|
|
1156
|
-
case "grep":
|
|
1157
|
-
return input["path"] ? [{ path: input["path"] }] : []
|
|
1158
|
-
case "bash":
|
|
1159
|
-
return []
|
|
1160
|
-
case "list":
|
|
1161
|
-
return input["path"] ? [{ path: input["path"] }] : []
|
|
1162
|
-
default:
|
|
1163
|
-
return []
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
async function defaultModel(config: ACPConfig, cwd?: string) {
|
|
1168
|
-
const sdk = config.sdk
|
|
1169
|
-
const configured = config.defaultModel
|
|
1170
|
-
if (configured) return configured
|
|
1171
|
-
|
|
1172
|
-
const directory = cwd ?? process.cwd()
|
|
1173
|
-
|
|
1174
|
-
const specified = await sdk.config
|
|
1175
|
-
.get({ directory }, { throwOnError: true })
|
|
1176
|
-
.then((resp) => {
|
|
1177
|
-
const cfg = resp.data
|
|
1178
|
-
if (!cfg || !cfg.model) return undefined
|
|
1179
|
-
const parsed = Provider.parseModel(cfg.model)
|
|
1180
|
-
return {
|
|
1181
|
-
providerID: parsed.providerID,
|
|
1182
|
-
modelID: parsed.modelID,
|
|
1183
|
-
}
|
|
1184
|
-
})
|
|
1185
|
-
.catch((error) => {
|
|
1186
|
-
log.error("failed to load user config for default model", { error })
|
|
1187
|
-
return undefined
|
|
1188
|
-
})
|
|
1189
|
-
|
|
1190
|
-
const providers = await sdk.config
|
|
1191
|
-
.providers({ directory }, { throwOnError: true })
|
|
1192
|
-
.then((x) => x.data?.providers ?? [])
|
|
1193
|
-
.catch((error) => {
|
|
1194
|
-
log.error("failed to list providers for default model", { error })
|
|
1195
|
-
return []
|
|
1196
|
-
})
|
|
1197
|
-
|
|
1198
|
-
if (specified && providers.length) {
|
|
1199
|
-
const provider = providers.find((p) => p.id === specified.providerID)
|
|
1200
|
-
if (provider && provider.models[specified.modelID]) return specified
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if (specified && !providers.length) return specified
|
|
1204
|
-
|
|
1205
|
-
const opencodeProvider = providers.find((p) => p.id === "opencode")
|
|
1206
|
-
if (opencodeProvider) {
|
|
1207
|
-
if (opencodeProvider.models["big-pickle"]) {
|
|
1208
|
-
return { providerID: "opencode", modelID: "big-pickle" }
|
|
1209
|
-
}
|
|
1210
|
-
const [best] = Provider.sort(Object.values(opencodeProvider.models))
|
|
1211
|
-
if (best) {
|
|
1212
|
-
return {
|
|
1213
|
-
providerID: best.providerID,
|
|
1214
|
-
modelID: best.id,
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
const models = providers.flatMap((p) => Object.values(p.models))
|
|
1220
|
-
const [best] = Provider.sort(models)
|
|
1221
|
-
if (best) {
|
|
1222
|
-
return {
|
|
1223
|
-
providerID: best.providerID,
|
|
1224
|
-
modelID: best.id,
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
if (specified) return specified
|
|
1229
|
-
|
|
1230
|
-
return { providerID: "opencode", modelID: "big-pickle" }
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
function parseUri(
|
|
1234
|
-
uri: string,
|
|
1235
|
-
): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
|
|
1236
|
-
try {
|
|
1237
|
-
if (uri.startsWith("file://")) {
|
|
1238
|
-
const path = uri.slice(7)
|
|
1239
|
-
const name = path.split("/").pop() || path
|
|
1240
|
-
return {
|
|
1241
|
-
type: "file",
|
|
1242
|
-
url: uri,
|
|
1243
|
-
filename: name,
|
|
1244
|
-
mime: "text/plain",
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
if (uri.startsWith("zed://")) {
|
|
1248
|
-
const url = new URL(uri)
|
|
1249
|
-
const path = url.searchParams.get("path")
|
|
1250
|
-
if (path) {
|
|
1251
|
-
const name = path.split("/").pop() || path
|
|
1252
|
-
return {
|
|
1253
|
-
type: "file",
|
|
1254
|
-
url: `file://${path}`,
|
|
1255
|
-
filename: name,
|
|
1256
|
-
mime: "text/plain",
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
type: "text",
|
|
1262
|
-
text: uri,
|
|
1263
|
-
}
|
|
1264
|
-
} catch {
|
|
1265
|
-
return {
|
|
1266
|
-
type: "text",
|
|
1267
|
-
text: uri,
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined {
|
|
1273
|
-
const result = applyPatch(fileOriginal, unifiedDiff)
|
|
1274
|
-
if (result === false) {
|
|
1275
|
-
log.error("Failed to apply unified diff (context mismatch)")
|
|
1276
|
-
return undefined
|
|
1277
|
-
}
|
|
1278
|
-
return result
|
|
1279
|
-
}
|
|
1280
|
-
}
|