anymous 1.0.0
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/README.md +15 -0
- package/bin/anymous.cjs +45 -0
- package/package.json +155 -0
- package/src/account/account.ts +473 -0
- package/src/account/repo.ts +171 -0
- package/src/account/schema.ts +99 -0
- package/src/account/url.ts +8 -0
- package/src/acp/agent.ts +95 -0
- package/src/acp/config-option.ts +203 -0
- package/src/acp/content.ts +269 -0
- package/src/acp/directory.ts +212 -0
- package/src/acp/error.ts +97 -0
- package/src/acp/event.ts +342 -0
- package/src/acp/permission.ts +254 -0
- package/src/acp/profile.ts +42 -0
- package/src/acp/service.ts +1097 -0
- package/src/acp/session.ts +232 -0
- package/src/acp/tool.ts +364 -0
- package/src/acp/usage.ts +239 -0
- package/src/agent/agent.ts +581 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +9 -0
- package/src/agent/prompt/debug-tools.txt +41 -0
- package/src/agent/prompt/exe-extractor.txt +37 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/memory-dump.txt +34 -0
- package/src/agent/prompt/reverser-automator.txt +24 -0
- package/src/agent/prompt/reverser-binary.txt +25 -0
- package/src/agent/prompt/reverser-dynamic.txt +29 -0
- package/src/agent/prompt/reverser-source.txt +22 -0
- package/src/agent/prompt/reverser-static.txt +28 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/agent/subagent-permissions.ts +27 -0
- package/src/audio.d.ts +14 -0
- package/src/auth/index.ts +97 -0
- package/src/background/job.ts +37 -0
- package/src/bus/global.ts +22 -0
- package/src/cli/bootstrap.ts +11 -0
- package/src/cli/cmd/account.ts +264 -0
- package/src/cli/cmd/acp.ts +73 -0
- package/src/cli/cmd/agent.ts +259 -0
- package/src/cli/cmd/attach.ts +148 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/db.ts +62 -0
- package/src/cli/cmd/debug/agent.handler.ts +193 -0
- package/src/cli/cmd/debug/agent.ts +27 -0
- package/src/cli/cmd/debug/config.ts +14 -0
- package/src/cli/cmd/debug/file.ts +73 -0
- package/src/cli/cmd/debug/index.ts +87 -0
- package/src/cli/cmd/debug/lsp.ts +50 -0
- package/src/cli/cmd/debug/ripgrep.ts +79 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +50 -0
- package/src/cli/cmd/debug/startup.ts +11 -0
- package/src/cli/cmd/debug/v2.ts +42 -0
- package/src/cli/cmd/export.ts +292 -0
- package/src/cli/cmd/generate.ts +54 -0
- package/src/cli/cmd/github.handler.ts +1593 -0
- package/src/cli/cmd/github.shared.ts +30 -0
- package/src/cli/cmd/github.ts +42 -0
- package/src/cli/cmd/import.ts +230 -0
- package/src/cli/cmd/mcp.ts +840 -0
- package/src/cli/cmd/models.ts +66 -0
- package/src/cli/cmd/plug.ts +230 -0
- package/src/cli/cmd/pr.ts +115 -0
- package/src/cli/cmd/prompt-display.ts +1 -0
- package/src/cli/cmd/providers.ts +534 -0
- package/src/cli/cmd/run/demo.ts +1274 -0
- package/src/cli/cmd/run/entry.body.ts +205 -0
- package/src/cli/cmd/run/footer.command.tsx +1070 -0
- package/src/cli/cmd/run/footer.menu.tsx +351 -0
- package/src/cli/cmd/run/footer.permission.tsx +472 -0
- package/src/cli/cmd/run/footer.prompt.tsx +1315 -0
- package/src/cli/cmd/run/footer.question.tsx +573 -0
- package/src/cli/cmd/run/footer.subagent.tsx +175 -0
- package/src/cli/cmd/run/footer.ts +1129 -0
- package/src/cli/cmd/run/footer.view.tsx +945 -0
- package/src/cli/cmd/run/footer.width.ts +27 -0
- package/src/cli/cmd/run/permission.shared.ts +256 -0
- package/src/cli/cmd/run/prompt.editor.ts +157 -0
- package/src/cli/cmd/run/prompt.shared.ts +153 -0
- package/src/cli/cmd/run/question.shared.ts +340 -0
- package/src/cli/cmd/run/runtime.boot.ts +202 -0
- package/src/cli/cmd/run/runtime.lifecycle.ts +406 -0
- package/src/cli/cmd/run/runtime.queue.ts +349 -0
- package/src/cli/cmd/run/runtime.shared.ts +17 -0
- package/src/cli/cmd/run/runtime.stdin.ts +37 -0
- package/src/cli/cmd/run/runtime.ts +814 -0
- package/src/cli/cmd/run/scrollback.shared.ts +92 -0
- package/src/cli/cmd/run/scrollback.surface.ts +431 -0
- package/src/cli/cmd/run/scrollback.writer.tsx +352 -0
- package/src/cli/cmd/run/session-data.ts +1113 -0
- package/src/cli/cmd/run/session-replay.ts +374 -0
- package/src/cli/cmd/run/session.shared.ts +196 -0
- package/src/cli/cmd/run/splash.ts +280 -0
- package/src/cli/cmd/run/stream.transport.ts +1462 -0
- package/src/cli/cmd/run/stream.ts +175 -0
- package/src/cli/cmd/run/subagent-data.ts +876 -0
- package/src/cli/cmd/run/theme.ts +690 -0
- package/src/cli/cmd/run/tool.ts +1486 -0
- package/src/cli/cmd/run/trace.ts +94 -0
- package/src/cli/cmd/run/turn-summary.ts +47 -0
- package/src/cli/cmd/run/types.ts +350 -0
- package/src/cli/cmd/run/variant.shared.ts +216 -0
- package/src/cli/cmd/run.ts +1011 -0
- package/src/cli/cmd/serve.ts +24 -0
- package/src/cli/cmd/session.ts +147 -0
- package/src/cli/cmd/stats.ts +393 -0
- package/src/cli/cmd/tui.ts +309 -0
- package/src/cli/cmd/uninstall.ts +353 -0
- package/src/cli/cmd/upgrade.ts +74 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/effect/prompt.ts +37 -0
- package/src/cli/effect-cmd.ts +96 -0
- package/src/cli/error.ts +130 -0
- package/src/cli/heap.ts +45 -0
- package/src/cli/logo.ts +1 -0
- package/src/cli/network.ts +80 -0
- package/src/cli/tui/layer.ts +8 -0
- package/src/cli/tui/validate-session.ts +29 -0
- package/src/cli/tui/worker.ts +80 -0
- package/src/cli/ui.ts +132 -0
- package/src/cli/upgrade.ts +53 -0
- package/src/command/index.ts +177 -0
- package/src/command/template/initialize.txt +66 -0
- package/src/command/template/review.txt +101 -0
- package/src/config/agent.ts +59 -0
- package/src/config/command.ts +39 -0
- package/src/config/config.ts +681 -0
- package/src/config/entry-name.ts +19 -0
- package/src/config/managed.ts +69 -0
- package/src/config/markdown.ts +36 -0
- package/src/config/parse.ts +79 -0
- package/src/config/paths.ts +45 -0
- package/src/config/plugin.ts +79 -0
- package/src/config/tui-cwd.ts +5 -0
- package/src/config/tui-host-attention.ts +21 -0
- package/src/config/tui-migrate.ts +132 -0
- package/src/config/tui.ts +276 -0
- package/src/config/variable.ts +91 -0
- package/src/control-plane/adapters/index.ts +41 -0
- package/src/control-plane/adapters/worktree.ts +96 -0
- package/src/control-plane/dev/README.md +19 -0
- package/src/control-plane/dev/debug-workspace-plugin.ts +73 -0
- package/src/control-plane/types.ts +59 -0
- package/src/control-plane/util.ts +39 -0
- package/src/control-plane/workspace-adapter-runtime.ts +51 -0
- package/src/control-plane/workspace-context.ts +26 -0
- package/src/control-plane/workspace.ts +964 -0
- package/src/effect/app-node-builder-v1.ts +12 -0
- package/src/effect/app-runtime.ts +135 -0
- package/src/effect/bootstrap-runtime.ts +19 -0
- package/src/effect/bridge.ts +84 -0
- package/src/effect/config-service.ts +67 -0
- package/src/effect/instance-ref.ts +11 -0
- package/src/effect/instance-registry.ts +12 -0
- package/src/effect/instance-state.ts +69 -0
- package/src/effect/promise.ts +17 -0
- package/src/effect/run-service.ts +47 -0
- package/src/effect/runner.ts +217 -0
- package/src/effect/runtime-flags.ts +78 -0
- package/src/env/index.ts +41 -0
- package/src/event-manifest.ts +3 -0
- package/src/event-v2-bridge.ts +71 -0
- package/src/format/formatter.ts +404 -0
- package/src/format/index.ts +203 -0
- package/src/git/index.ts +348 -0
- package/src/id/id.ts +80 -0
- package/src/ide/index.ts +54 -0
- package/src/image/image.ts +172 -0
- package/src/index.ts +142 -0
- package/src/installation/index.ts +336 -0
- package/src/lsp/client.ts +650 -0
- package/src/lsp/diagnostic.ts +29 -0
- package/src/lsp/language.ts +121 -0
- package/src/lsp/launch.ts +21 -0
- package/src/lsp/lsp.ts +507 -0
- package/src/lsp/server.ts +1983 -0
- package/src/markdown.d.ts +4 -0
- package/src/mcp/auth.ts +163 -0
- package/src/mcp/browser.ts +37 -0
- package/src/mcp/catalog.ts +170 -0
- package/src/mcp/index.ts +1004 -0
- package/src/mcp/oauth-callback.ts +194 -0
- package/src/mcp/oauth-provider.ts +259 -0
- package/src/node.ts +4 -0
- package/src/patch/index.ts +686 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/evaluate.ts +1 -0
- package/src/permission/index.ts +229 -0
- package/src/plugin/azure.ts +26 -0
- package/src/plugin/cloudflare.ts +76 -0
- package/src/plugin/digitalocean.ts +325 -0
- package/src/plugin/github-copilot/copilot.ts +414 -0
- package/src/plugin/github-copilot/models.ts +258 -0
- package/src/plugin/index.ts +314 -0
- package/src/plugin/install.ts +439 -0
- package/src/plugin/loader.ts +237 -0
- package/src/plugin/meta.ts +188 -0
- package/src/plugin/openai/README.md +31 -0
- package/src/plugin/openai/codex.ts +565 -0
- package/src/plugin/openai/ws-pool.ts +270 -0
- package/src/plugin/openai/ws.ts +381 -0
- package/src/plugin/pty-environment.ts +24 -0
- package/src/plugin/shared.ts +323 -0
- package/src/plugin/snowflake-cortex.ts +507 -0
- package/src/plugin/tui/internal.ts +10 -0
- package/src/plugin/tui/runtime.ts +1131 -0
- package/src/plugin/xai.ts +626 -0
- package/src/project/bootstrap-service.ts +9 -0
- package/src/project/bootstrap.ts +58 -0
- package/src/project/instance-context.ts +24 -0
- package/src/project/instance-runtime.ts +16 -0
- package/src/project/instance-store.ts +213 -0
- package/src/project/project.ts +483 -0
- package/src/project/vcs.ts +423 -0
- package/src/provider/auth.ts +229 -0
- package/src/provider/error.ts +188 -0
- package/src/provider/model-status.ts +8 -0
- package/src/provider/provider.ts +2006 -0
- package/src/provider/transform.ts +1832 -0
- package/src/question/index.ts +161 -0
- package/src/question/schema.ts +4 -0
- package/src/server/auth.ts +48 -0
- package/src/server/event.ts +10 -0
- package/src/server/global-lifecycle.ts +28 -0
- package/src/server/init-projectors.ts +3 -0
- package/src/server/mdns.ts +47 -0
- package/src/server/projectors.ts +1 -0
- package/src/server/proxy-util.ts +48 -0
- package/src/server/routes/instance/httpapi/AGENTS.md +39 -0
- package/src/server/routes/instance/httpapi/api.ts +97 -0
- package/src/server/routes/instance/httpapi/errors.ts +193 -0
- package/src/server/routes/instance/httpapi/groups/config.ts +65 -0
- package/src/server/routes/instance/httpapi/groups/control-plane.ts +35 -0
- package/src/server/routes/instance/httpapi/groups/control.ts +76 -0
- package/src/server/routes/instance/httpapi/groups/event.ts +29 -0
- package/src/server/routes/instance/httpapi/groups/experimental.ts +275 -0
- package/src/server/routes/instance/httpapi/groups/file.ts +185 -0
- package/src/server/routes/instance/httpapi/groups/global.ts +136 -0
- package/src/server/routes/instance/httpapi/groups/instance.ts +206 -0
- package/src/server/routes/instance/httpapi/groups/mcp.ts +156 -0
- package/src/server/routes/instance/httpapi/groups/metadata.ts +18 -0
- package/src/server/routes/instance/httpapi/groups/permission.ts +61 -0
- package/src/server/routes/instance/httpapi/groups/project-copy.ts +32 -0
- package/src/server/routes/instance/httpapi/groups/project.ts +93 -0
- package/src/server/routes/instance/httpapi/groups/provider.ts +101 -0
- package/src/server/routes/instance/httpapi/groups/pty.ts +172 -0
- package/src/server/routes/instance/httpapi/groups/query.ts +12 -0
- package/src/server/routes/instance/httpapi/groups/question.ts +74 -0
- package/src/server/routes/instance/httpapi/groups/session.ts +462 -0
- package/src/server/routes/instance/httpapi/groups/sync.ts +113 -0
- package/src/server/routes/instance/httpapi/groups/tui.ts +208 -0
- package/src/server/routes/instance/httpapi/groups/workspace.ts +141 -0
- package/src/server/routes/instance/httpapi/handlers/config.ts +34 -0
- package/src/server/routes/instance/httpapi/handlers/control-plane.ts +37 -0
- package/src/server/routes/instance/httpapi/handlers/control.ts +43 -0
- package/src/server/routes/instance/httpapi/handlers/event.ts +99 -0
- package/src/server/routes/instance/httpapi/handlers/experimental.ts +193 -0
- package/src/server/routes/instance/httpapi/handlers/file.ts +139 -0
- package/src/server/routes/instance/httpapi/handlers/global.ts +156 -0
- package/src/server/routes/instance/httpapi/handlers/instance.ts +110 -0
- package/src/server/routes/instance/httpapi/handlers/mcp.ts +111 -0
- package/src/server/routes/instance/httpapi/handlers/permission.ts +41 -0
- package/src/server/routes/instance/httpapi/handlers/project-copy.ts +83 -0
- package/src/server/routes/instance/httpapi/handlers/project.ts +63 -0
- package/src/server/routes/instance/httpapi/handlers/provider.ts +113 -0
- package/src/server/routes/instance/httpapi/handlers/pty.ts +273 -0
- package/src/server/routes/instance/httpapi/handlers/question.ts +54 -0
- package/src/server/routes/instance/httpapi/handlers/session-errors.ts +21 -0
- package/src/server/routes/instance/httpapi/handlers/session.ts +442 -0
- package/src/server/routes/instance/httpapi/handlers/sync.ts +89 -0
- package/src/server/routes/instance/httpapi/handlers/tui.ts +131 -0
- package/src/server/routes/instance/httpapi/handlers/workspace.ts +102 -0
- package/src/server/routes/instance/httpapi/lifecycle.ts +54 -0
- package/src/server/routes/instance/httpapi/middleware/authorization.ts +150 -0
- package/src/server/routes/instance/httpapi/middleware/compression.ts +64 -0
- package/src/server/routes/instance/httpapi/middleware/cors-vary.ts +29 -0
- package/src/server/routes/instance/httpapi/middleware/error.ts +43 -0
- package/src/server/routes/instance/httpapi/middleware/fence.ts +25 -0
- package/src/server/routes/instance/httpapi/middleware/instance-context.ts +43 -0
- package/src/server/routes/instance/httpapi/middleware/proxy.ts +108 -0
- package/src/server/routes/instance/httpapi/middleware/schema-error.ts +41 -0
- package/src/server/routes/instance/httpapi/middleware/workspace-routing.ts +250 -0
- package/src/server/routes/instance/httpapi/public.ts +537 -0
- package/src/server/routes/instance/httpapi/server.ts +325 -0
- package/src/server/routes/instance/httpapi/websocket-tracker.ts +60 -0
- package/src/server/server.ts +226 -0
- package/src/server/shared/fence.ts +60 -0
- package/src/server/shared/pty-ticket.ts +15 -0
- package/src/server/shared/public-ui.ts +12 -0
- package/src/server/shared/tui-control.ts +28 -0
- package/src/server/shared/ui.ts +108 -0
- package/src/server/shared/workspace-routing.ts +38 -0
- package/src/server/tui-event.ts +1 -0
- package/src/session/compaction.ts +562 -0
- package/src/session/instruction.ts +237 -0
- package/src/session/llm/AGENTS.md +90 -0
- package/src/session/llm/ai-sdk.ts +288 -0
- package/src/session/llm/native-request.ts +196 -0
- package/src/session/llm/native-runtime.ts +195 -0
- package/src/session/llm/request.ts +226 -0
- package/src/session/llm.ts +404 -0
- package/src/session/message-error.ts +14 -0
- package/src/session/message-v2.ts +734 -0
- package/src/session/message.ts +148 -0
- package/src/session/overflow.ts +34 -0
- package/src/session/processor.ts +718 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +79 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/default.txt +95 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/gpt.txt +107 -0
- package/src/session/prompt/kimi.txt +95 -0
- package/src/session/prompt/meta.txt +65 -0
- package/src/session/prompt/plan-mode.txt +70 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/trinity.txt +97 -0
- package/src/session/prompt.ts +1631 -0
- package/src/session/reminders.ts +92 -0
- package/src/session/retry.ts +201 -0
- package/src/session/revert.ts +146 -0
- package/src/session/run-state.ts +151 -0
- package/src/session/schema.ts +26 -0
- package/src/session/session.ts +1018 -0
- package/src/session/status.ts +56 -0
- package/src/session/summary.ts +160 -0
- package/src/session/system.ts +145 -0
- package/src/session/todo.ts +74 -0
- package/src/session/tools.ts +590 -0
- package/src/share/session.ts +58 -0
- package/src/share/share-next.ts +371 -0
- package/src/skill/discovery.ts +140 -0
- package/src/skill/index.ts +354 -0
- package/src/snapshot/index.ts +807 -0
- package/src/sql.d.ts +4 -0
- package/src/storage/schema.ts +5 -0
- package/src/storage/storage.ts +327 -0
- package/src/sync/README.md +179 -0
- package/src/sync/schema.ts +11 -0
- package/src/temporary.ts +31 -0
- package/src/tool/apply_patch.ts +313 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/code-mode.ts +310 -0
- package/src/tool/edit.ts +737 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +49 -0
- package/src/tool/glob.ts +76 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +115 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +21 -0
- package/src/tool/json-schema.ts +164 -0
- package/src/tool/lsp.ts +113 -0
- package/src/tool/lsp.txt +24 -0
- package/src/tool/mcp-websearch.ts +96 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +79 -0
- package/src/tool/question.ts +44 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +386 -0
- package/src/tool/read.txt +14 -0
- package/src/tool/registry.ts +450 -0
- package/src/tool/schema.ts +14 -0
- package/src/tool/shell/id.ts +19 -0
- package/src/tool/shell/prompt.ts +293 -0
- package/src/tool/shell/shell.txt +21 -0
- package/src/tool/shell.ts +645 -0
- package/src/tool/skill.ts +70 -0
- package/src/tool/skill.txt +5 -0
- package/src/tool/task.ts +360 -0
- package/src/tool/task.txt +19 -0
- package/src/tool/todo.ts +46 -0
- package/src/tool/todowrite.txt +44 -0
- package/src/tool/tool.ts +183 -0
- package/src/tool/truncate.ts +156 -0
- package/src/tool/truncation-dir.ts +4 -0
- package/src/tool/webfetch.ts +192 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +143 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +104 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +17 -0
- package/src/util/bom.ts +27 -0
- package/src/util/data-url.ts +9 -0
- package/src/util/defer.ts +10 -0
- package/src/util/effect-http-client.ts +11 -0
- package/src/util/error.ts +1 -0
- package/src/util/filesystem.ts +251 -0
- package/src/util/html.ts +8 -0
- package/src/util/iife.ts +3 -0
- package/src/util/lazy.ts +20 -0
- package/src/util/local-context.ts +25 -0
- package/src/util/locale.ts +2 -0
- package/src/util/media.ts +26 -0
- package/src/util/process.ts +177 -0
- package/src/util/proxy-env.ts +72 -0
- package/src/util/queue.ts +32 -0
- package/src/util/record.ts +1 -0
- package/src/util/repository.ts +232 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +13 -0
- package/src/util/token.ts +1 -0
- package/src/util/wildcard.ts +59 -0
- package/src/worktree/index.ts +623 -0
- package/tsconfig.json +16 -0
package/src/git/index.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
2
|
+
import { AppProcess } from "@opencode-ai/core/process"
|
|
3
|
+
import { Effect, Layer, Context, Stream } from "effect"
|
|
4
|
+
import { ChildProcess } from "effect/unstable/process"
|
|
5
|
+
|
|
6
|
+
const cfg = [
|
|
7
|
+
"--no-optional-locks",
|
|
8
|
+
"-c",
|
|
9
|
+
"core.autocrlf=false",
|
|
10
|
+
"-c",
|
|
11
|
+
"core.fsmonitor=false",
|
|
12
|
+
"-c",
|
|
13
|
+
"core.longpaths=true",
|
|
14
|
+
"-c",
|
|
15
|
+
"core.symlinks=true",
|
|
16
|
+
"-c",
|
|
17
|
+
"core.quotepath=false",
|
|
18
|
+
] as const
|
|
19
|
+
|
|
20
|
+
const out = (result: { text(): string }) => result.text().trim()
|
|
21
|
+
const nuls = (text: string) => text.split("\0").filter(Boolean)
|
|
22
|
+
const fail = (err: unknown) =>
|
|
23
|
+
({
|
|
24
|
+
exitCode: 1,
|
|
25
|
+
text: () => "",
|
|
26
|
+
stdout: Buffer.alloc(0),
|
|
27
|
+
stderr: Buffer.from(err instanceof Error ? err.message : String(err)),
|
|
28
|
+
truncated: false,
|
|
29
|
+
}) satisfies Result
|
|
30
|
+
|
|
31
|
+
export type Kind = "added" | "deleted" | "modified"
|
|
32
|
+
|
|
33
|
+
export type Base = {
|
|
34
|
+
readonly name: string
|
|
35
|
+
readonly ref: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type Item = {
|
|
39
|
+
readonly file: string
|
|
40
|
+
readonly code: string
|
|
41
|
+
readonly status: Kind
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Stat = {
|
|
45
|
+
readonly file: string
|
|
46
|
+
readonly additions: number
|
|
47
|
+
readonly deletions: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type Patch = {
|
|
51
|
+
readonly text: string
|
|
52
|
+
readonly truncated: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PatchOptions {
|
|
56
|
+
readonly context?: number
|
|
57
|
+
readonly maxOutputBytes?: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface Result {
|
|
61
|
+
readonly exitCode: number
|
|
62
|
+
readonly text: () => string
|
|
63
|
+
readonly stdout: Buffer
|
|
64
|
+
readonly stderr: Buffer
|
|
65
|
+
readonly truncated: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface Options {
|
|
69
|
+
readonly cwd: string
|
|
70
|
+
readonly env?: Record<string, string>
|
|
71
|
+
readonly maxOutputBytes?: number
|
|
72
|
+
readonly stdin?: ChildProcess.CommandInput
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface Interface {
|
|
76
|
+
readonly run: (args: string[], opts: Options) => Effect.Effect<Result>
|
|
77
|
+
readonly branch: (cwd: string) => Effect.Effect<string | undefined>
|
|
78
|
+
readonly prefix: (cwd: string) => Effect.Effect<string>
|
|
79
|
+
readonly defaultBranch: (cwd: string) => Effect.Effect<Base | undefined>
|
|
80
|
+
readonly hasHead: (cwd: string) => Effect.Effect<boolean>
|
|
81
|
+
readonly mergeBase: (cwd: string, base: string, head?: string) => Effect.Effect<string | undefined>
|
|
82
|
+
readonly show: (cwd: string, ref: string, file: string, prefix?: string) => Effect.Effect<string>
|
|
83
|
+
readonly status: (cwd: string) => Effect.Effect<Item[]>
|
|
84
|
+
readonly diff: (cwd: string, ref: string) => Effect.Effect<Item[]>
|
|
85
|
+
readonly stats: (cwd: string, ref: string) => Effect.Effect<Stat[]>
|
|
86
|
+
readonly patch: (cwd: string, ref: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
|
|
87
|
+
readonly patchAll: (cwd: string, ref: string, options?: PatchOptions) => Effect.Effect<Patch>
|
|
88
|
+
readonly patchUntracked: (cwd: string, file: string, options?: PatchOptions) => Effect.Effect<Patch>
|
|
89
|
+
readonly statUntracked: (cwd: string, file: string) => Effect.Effect<Stat | undefined>
|
|
90
|
+
readonly applyPatch: (cwd: string, patch: string) => Effect.Effect<Result>
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const kind = (code: string): Kind => {
|
|
94
|
+
if (code === "??") return "added"
|
|
95
|
+
if (code.includes("U")) return "modified"
|
|
96
|
+
if (code.includes("A") && !code.includes("D")) return "added"
|
|
97
|
+
if (code.includes("D") && !code.includes("A")) return "deleted"
|
|
98
|
+
return "modified"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export class Service extends Context.Service<Service, Interface>()("@opencode/Git") {}
|
|
102
|
+
|
|
103
|
+
const layer = Layer.effect(
|
|
104
|
+
Service,
|
|
105
|
+
Effect.gen(function* () {
|
|
106
|
+
const appProcess = yield* AppProcess.Service
|
|
107
|
+
const encoder = new TextEncoder()
|
|
108
|
+
const stdin = (text: string) => Stream.make(encoder.encode(text))
|
|
109
|
+
|
|
110
|
+
const run = Effect.fn("Git.run")(
|
|
111
|
+
function* (args: string[], opts: Options) {
|
|
112
|
+
const result = yield* appProcess.run(
|
|
113
|
+
ChildProcess.make("git", [...cfg, ...args], {
|
|
114
|
+
cwd: opts.cwd,
|
|
115
|
+
env: opts.env,
|
|
116
|
+
extendEnv: true,
|
|
117
|
+
stdin: opts.stdin ?? "ignore",
|
|
118
|
+
stdout: "pipe",
|
|
119
|
+
stderr: "pipe",
|
|
120
|
+
}),
|
|
121
|
+
{ maxOutputBytes: opts.maxOutputBytes },
|
|
122
|
+
)
|
|
123
|
+
return {
|
|
124
|
+
exitCode: result.exitCode,
|
|
125
|
+
text: () => result.stdout.toString("utf8"),
|
|
126
|
+
stdout: result.stdout,
|
|
127
|
+
stderr: result.stderr,
|
|
128
|
+
truncated: result.stdoutTruncated || result.stderrTruncated,
|
|
129
|
+
} satisfies Result
|
|
130
|
+
},
|
|
131
|
+
Effect.catch((err) => Effect.succeed(fail(err))),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const text = Effect.fn("Git.text")(function* (args: string[], opts: Options) {
|
|
135
|
+
return (yield* run(args, opts)).text()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const lines = Effect.fn("Git.lines")(function* (args: string[], opts: Options) {
|
|
139
|
+
return (yield* text(args, opts))
|
|
140
|
+
.split(/\r?\n/)
|
|
141
|
+
.map((item) => item.trim())
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const refs = Effect.fnUntraced(function* (cwd: string) {
|
|
146
|
+
return yield* lines(["for-each-ref", "--format=%(refname:short)", "refs/heads"], { cwd })
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const configured = Effect.fnUntraced(function* (cwd: string, list: string[]) {
|
|
150
|
+
const result = yield* run(["config", "init.defaultBranch"], { cwd })
|
|
151
|
+
const name = out(result)
|
|
152
|
+
if (!name || !list.includes(name)) return
|
|
153
|
+
return { name, ref: name } satisfies Base
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const primary = Effect.fnUntraced(function* (cwd: string) {
|
|
157
|
+
const list = yield* lines(["remote"], { cwd })
|
|
158
|
+
if (list.includes("origin")) return "origin"
|
|
159
|
+
if (list.length === 1) return list[0]
|
|
160
|
+
if (list.includes("upstream")) return "upstream"
|
|
161
|
+
return list[0]
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const branch = Effect.fn("Git.branch")(function* (cwd: string) {
|
|
165
|
+
const result = yield* run(["symbolic-ref", "--quiet", "--short", "HEAD"], { cwd })
|
|
166
|
+
if (result.exitCode !== 0) return
|
|
167
|
+
const text = out(result)
|
|
168
|
+
return text || undefined
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const prefix = Effect.fn("Git.prefix")(function* (cwd: string) {
|
|
172
|
+
const result = yield* run(["rev-parse", "--show-prefix"], { cwd })
|
|
173
|
+
if (result.exitCode !== 0) return ""
|
|
174
|
+
return out(result)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const defaultBranch = Effect.fn("Git.defaultBranch")(function* (cwd: string) {
|
|
178
|
+
const remote = yield* primary(cwd)
|
|
179
|
+
if (remote) {
|
|
180
|
+
const head = yield* run(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd })
|
|
181
|
+
if (head.exitCode === 0) {
|
|
182
|
+
const ref = out(head).replace(/^refs\/remotes\//, "")
|
|
183
|
+
const name = ref.startsWith(`${remote}/`) ? ref.slice(`${remote}/`.length) : ""
|
|
184
|
+
if (name) return { name, ref } satisfies Base
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const list = yield* refs(cwd)
|
|
189
|
+
const next = yield* configured(cwd, list)
|
|
190
|
+
if (next) return next
|
|
191
|
+
if (list.includes("main")) return { name: "main", ref: "main" } satisfies Base
|
|
192
|
+
if (list.includes("master")) return { name: "master", ref: "master" } satisfies Base
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const hasHead = Effect.fn("Git.hasHead")(function* (cwd: string) {
|
|
196
|
+
const result = yield* run(["rev-parse", "--verify", "HEAD"], { cwd })
|
|
197
|
+
return result.exitCode === 0
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const mergeBase = Effect.fn("Git.mergeBase")(function* (cwd: string, base: string, head = "HEAD") {
|
|
201
|
+
const result = yield* run(["merge-base", base, head], { cwd })
|
|
202
|
+
if (result.exitCode !== 0) return
|
|
203
|
+
const text = out(result)
|
|
204
|
+
return text || undefined
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const show = Effect.fn("Git.show")(function* (cwd: string, ref: string, file: string, prefix = "") {
|
|
208
|
+
const target = prefix ? `${prefix}${file}` : file
|
|
209
|
+
const result = yield* run(["show", `${ref}:${target}`], { cwd })
|
|
210
|
+
if (result.exitCode !== 0) return ""
|
|
211
|
+
if (result.stdout.includes(0)) return ""
|
|
212
|
+
return result.text()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const status = Effect.fn("Git.status")(function* (cwd: string) {
|
|
216
|
+
return nuls(
|
|
217
|
+
yield* text(["status", "--porcelain=v1", "--untracked-files=all", "--no-renames", "-z", "--", "."], {
|
|
218
|
+
cwd,
|
|
219
|
+
}),
|
|
220
|
+
).flatMap((item) => {
|
|
221
|
+
const file = item.slice(3)
|
|
222
|
+
if (!file) return []
|
|
223
|
+
const code = item.slice(0, 2)
|
|
224
|
+
return [{ file, code, status: kind(code) } satisfies Item]
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const diff = Effect.fn("Git.diff")(function* (cwd: string, ref: string) {
|
|
229
|
+
const list = nuls(
|
|
230
|
+
yield* text(["diff", "--no-ext-diff", "--no-renames", "--name-status", "-z", ref, "--", "."], { cwd }),
|
|
231
|
+
)
|
|
232
|
+
return list.flatMap((code, idx) => {
|
|
233
|
+
if (idx % 2 !== 0) return []
|
|
234
|
+
const file = list[idx + 1]
|
|
235
|
+
if (!code || !file) return []
|
|
236
|
+
return [{ file, code, status: kind(code) } satisfies Item]
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const stats = Effect.fn("Git.stats")(function* (cwd: string, ref: string) {
|
|
241
|
+
return nuls(
|
|
242
|
+
yield* text(["diff", "--no-ext-diff", "--no-renames", "--numstat", "-z", ref, "--", "."], { cwd }),
|
|
243
|
+
).flatMap((item) => {
|
|
244
|
+
const a = item.indexOf("\t")
|
|
245
|
+
const b = item.indexOf("\t", a + 1)
|
|
246
|
+
if (a === -1 || b === -1) return []
|
|
247
|
+
const file = item.slice(b + 1)
|
|
248
|
+
if (!file) return []
|
|
249
|
+
const adds = item.slice(0, a)
|
|
250
|
+
const dels = item.slice(a + 1, b)
|
|
251
|
+
const additions = adds === "-" ? 0 : Number.parseInt(adds || "0", 10)
|
|
252
|
+
const deletions = dels === "-" ? 0 : Number.parseInt(dels || "0", 10)
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
file,
|
|
256
|
+
additions: Number.isFinite(additions) ? additions : 0,
|
|
257
|
+
deletions: Number.isFinite(deletions) ? deletions : 0,
|
|
258
|
+
} satisfies Stat,
|
|
259
|
+
]
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const patch = Effect.fn("Git.patch")(function* (cwd: string, ref: string, file: string, options?: PatchOptions) {
|
|
264
|
+
const result = yield* run(
|
|
265
|
+
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "--", file],
|
|
266
|
+
{ cwd, maxOutputBytes: options?.maxOutputBytes },
|
|
267
|
+
)
|
|
268
|
+
return { text: result.truncated ? "" : result.text(), truncated: result.truncated } satisfies Patch
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const patchAll = Effect.fn("Git.patchAll")(function* (cwd: string, ref: string, options?: PatchOptions) {
|
|
272
|
+
const result = yield* run(
|
|
273
|
+
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`, ref, "--", "."],
|
|
274
|
+
{ cwd, maxOutputBytes: options?.maxOutputBytes },
|
|
275
|
+
)
|
|
276
|
+
return { text: result.text(), truncated: result.truncated } satisfies Patch
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const patchUntracked = Effect.fn("Git.patchUntracked")(function* (
|
|
280
|
+
cwd: string,
|
|
281
|
+
file: string,
|
|
282
|
+
options?: PatchOptions,
|
|
283
|
+
) {
|
|
284
|
+
const result = yield* run(
|
|
285
|
+
[
|
|
286
|
+
"diff",
|
|
287
|
+
"--no-index",
|
|
288
|
+
"--patch",
|
|
289
|
+
"--no-ext-diff",
|
|
290
|
+
"--no-renames",
|
|
291
|
+
`--unified=${options?.context ?? 3}`,
|
|
292
|
+
"--",
|
|
293
|
+
"/dev/null",
|
|
294
|
+
file,
|
|
295
|
+
],
|
|
296
|
+
{ cwd, maxOutputBytes: options?.maxOutputBytes },
|
|
297
|
+
)
|
|
298
|
+
return { text: result.truncated ? "" : result.text(), truncated: result.truncated } satisfies Patch
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const statUntracked = Effect.fn("Git.statUntracked")(function* (cwd: string, file: string) {
|
|
302
|
+
const result = yield* run(["diff", "--no-index", "--numstat", "--", "/dev/null", file], {
|
|
303
|
+
cwd,
|
|
304
|
+
maxOutputBytes: 4096,
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
if (result.truncated) return
|
|
308
|
+
const text = result.text()
|
|
309
|
+
|
|
310
|
+
const parts = text.split("\t")
|
|
311
|
+
if (parts.length < 2) return
|
|
312
|
+
|
|
313
|
+
const additions = parts[0] === "-" ? 0 : Number.parseInt(parts[0] || "0", 10)
|
|
314
|
+
const deletions = parts[1] === "-" ? 0 : Number.parseInt(parts[1] || "0", 10)
|
|
315
|
+
return {
|
|
316
|
+
file,
|
|
317
|
+
additions: Number.isFinite(additions) ? additions : 0,
|
|
318
|
+
deletions: Number.isFinite(deletions) ? deletions : 0,
|
|
319
|
+
} satisfies Stat
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
const applyPatch = Effect.fn("Git.applyPatch")(function* (cwd: string, patch: string) {
|
|
323
|
+
return yield* run(["apply", "-"], { cwd, stdin: stdin(patch) })
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
return Service.of({
|
|
327
|
+
run,
|
|
328
|
+
branch,
|
|
329
|
+
prefix,
|
|
330
|
+
defaultBranch,
|
|
331
|
+
hasHead,
|
|
332
|
+
mergeBase,
|
|
333
|
+
show,
|
|
334
|
+
status,
|
|
335
|
+
diff,
|
|
336
|
+
stats,
|
|
337
|
+
patch,
|
|
338
|
+
patchAll,
|
|
339
|
+
patchUntracked,
|
|
340
|
+
statUntracked,
|
|
341
|
+
applyPatch,
|
|
342
|
+
})
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AppProcess.node] })
|
|
347
|
+
|
|
348
|
+
export * as Git from "."
|
package/src/id/id.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { randomBytes } from "crypto"
|
|
2
|
+
|
|
3
|
+
const prefixes = {
|
|
4
|
+
job: "job",
|
|
5
|
+
event: "evt",
|
|
6
|
+
session: "ses",
|
|
7
|
+
message: "msg",
|
|
8
|
+
permission: "per",
|
|
9
|
+
question: "que",
|
|
10
|
+
part: "prt",
|
|
11
|
+
pty: "pty",
|
|
12
|
+
tool: "tool",
|
|
13
|
+
workspace: "wrk",
|
|
14
|
+
} as const
|
|
15
|
+
|
|
16
|
+
const LENGTH = 26
|
|
17
|
+
|
|
18
|
+
// State for monotonic ID generation
|
|
19
|
+
let lastTimestamp = 0
|
|
20
|
+
let counter = 0
|
|
21
|
+
|
|
22
|
+
export function ascending(prefix: keyof typeof prefixes, given?: string) {
|
|
23
|
+
return generateID(prefix, "ascending", given)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function descending(prefix: keyof typeof prefixes, given?: string) {
|
|
27
|
+
return generateID(prefix, "descending", given)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function generateID(prefix: keyof typeof prefixes, direction: "descending" | "ascending", given?: string): string {
|
|
31
|
+
if (!given) {
|
|
32
|
+
return create(prefixes[prefix], direction)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!given.startsWith(prefixes[prefix])) {
|
|
36
|
+
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`)
|
|
37
|
+
}
|
|
38
|
+
return given
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function randomBase62(length: number): string {
|
|
42
|
+
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
43
|
+
let result = ""
|
|
44
|
+
const bytes = randomBytes(length)
|
|
45
|
+
for (let i = 0; i < length; i++) {
|
|
46
|
+
result += chars[bytes[i] % 62]
|
|
47
|
+
}
|
|
48
|
+
return result
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string {
|
|
52
|
+
const currentTimestamp = timestamp ?? Date.now()
|
|
53
|
+
|
|
54
|
+
if (currentTimestamp !== lastTimestamp) {
|
|
55
|
+
lastTimestamp = currentTimestamp
|
|
56
|
+
counter = 0
|
|
57
|
+
}
|
|
58
|
+
counter++
|
|
59
|
+
|
|
60
|
+
let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter)
|
|
61
|
+
|
|
62
|
+
now = direction === "descending" ? ~now : now
|
|
63
|
+
|
|
64
|
+
const timeBytes = Buffer.alloc(6)
|
|
65
|
+
for (let i = 0; i < 6; i++) {
|
|
66
|
+
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return prefix + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Extract timestamp from an ascending ID. Does not work with descending IDs. */
|
|
73
|
+
export function timestamp(id: string): number {
|
|
74
|
+
const prefix = id.split("_")[0]
|
|
75
|
+
const hex = id.slice(prefix.length + 1, prefix.length + 13)
|
|
76
|
+
const encoded = BigInt("0x" + hex)
|
|
77
|
+
return Number(encoded / BigInt(0x1000))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export * as Identifier from "./id"
|
package/src/ide/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Schema } from "effect"
|
|
2
|
+
import { NamedError } from "@opencode-ai/core/util/error"
|
|
3
|
+
import { Process } from "@/util/process"
|
|
4
|
+
import { IdeEvent } from "@opencode-ai/schema/ide-event"
|
|
5
|
+
|
|
6
|
+
const SUPPORTED_IDES = [
|
|
7
|
+
{ name: "Windsurf" as const, cmd: "windsurf" },
|
|
8
|
+
{ name: "Visual Studio Code - Insiders" as const, cmd: "code-insiders" },
|
|
9
|
+
{ name: "Visual Studio Code" as const, cmd: "code" },
|
|
10
|
+
{ name: "Cursor" as const, cmd: "cursor" },
|
|
11
|
+
{ name: "VSCodium" as const, cmd: "codium" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export const Event = IdeEvent
|
|
15
|
+
|
|
16
|
+
export const AlreadyInstalledError = NamedError.create("AlreadyInstalledError", {})
|
|
17
|
+
|
|
18
|
+
export const InstallFailedError = NamedError.create("InstallFailedError", {
|
|
19
|
+
stderr: Schema.String,
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export function ide() {
|
|
23
|
+
if (process.env["TERM_PROGRAM"] === "vscode") {
|
|
24
|
+
const v = process.env["GIT_ASKPASS"]
|
|
25
|
+
for (const ide of SUPPORTED_IDES) {
|
|
26
|
+
if (v?.includes(ide.name)) return ide.name
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return "unknown"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function alreadyInstalled() {
|
|
33
|
+
return process.env["OPENCODE_CALLER"] === "vscode" || process.env["OPENCODE_CALLER"] === "vscode-insiders"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function install(ide: (typeof SUPPORTED_IDES)[number]["name"]) {
|
|
37
|
+
const cmd = SUPPORTED_IDES.find((i) => i.name === ide)?.cmd
|
|
38
|
+
if (!cmd) throw new Error(`Unknown IDE: ${ide}`)
|
|
39
|
+
|
|
40
|
+
const p = await Process.run([cmd, "--install-extension", "sst-dev.opencode"], {
|
|
41
|
+
nothrow: true,
|
|
42
|
+
})
|
|
43
|
+
const stdout = p.stdout.toString()
|
|
44
|
+
const stderr = p.stderr.toString()
|
|
45
|
+
|
|
46
|
+
if (p.code !== 0) {
|
|
47
|
+
throw new InstallFailedError({ stderr })
|
|
48
|
+
}
|
|
49
|
+
if (stdout.includes("already installed")) {
|
|
50
|
+
throw new AlreadyInstalledError({})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export * as Ide from "."
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
2
|
+
import { Config } from "@/config/config"
|
|
3
|
+
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
|
4
|
+
import type { MessageV2 } from "@/session/message-v2"
|
|
5
|
+
import photonWasm from "@silvia-odwyer/photon-node/photon_rs_bg.wasm" with { type: "file" }
|
|
6
|
+
import { Context, Effect, Layer, Schema } from "effect"
|
|
7
|
+
import path from "node:path"
|
|
8
|
+
import { fileURLToPath } from "node:url"
|
|
9
|
+
|
|
10
|
+
const MAX_BASE64_BYTES = 5 * 1024 * 1024
|
|
11
|
+
const MAX_WIDTH = 2000
|
|
12
|
+
const MAX_HEIGHT = 2000
|
|
13
|
+
const AUTO_RESIZE = true
|
|
14
|
+
const JPEG_QUALITIES = [80, 85, 70, 55, 40]
|
|
15
|
+
export class ResizerUnavailableError extends Schema.TaggedErrorClass<ResizerUnavailableError>()(
|
|
16
|
+
"ImageResizerUnavailableError",
|
|
17
|
+
{},
|
|
18
|
+
) {
|
|
19
|
+
override get message() {
|
|
20
|
+
return "Image resizer is unavailable"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class InvalidDataUrlError extends Schema.TaggedErrorClass<InvalidDataUrlError>()("ImageInvalidDataUrlError", {
|
|
25
|
+
url: Schema.String,
|
|
26
|
+
}) {
|
|
27
|
+
override get message() {
|
|
28
|
+
return "Image URL must be a base64 data URL"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class DecodeError extends Schema.TaggedErrorClass<DecodeError>()("ImageDecodeError", {}) {
|
|
33
|
+
override get message() {
|
|
34
|
+
return "Image could not be decoded"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class SizeError extends Schema.TaggedErrorClass<SizeError>()("ImageSizeError", {
|
|
39
|
+
bytes: Schema.Number,
|
|
40
|
+
max: Schema.Number,
|
|
41
|
+
width: Schema.Number,
|
|
42
|
+
height: Schema.Number,
|
|
43
|
+
max_width: Schema.Number,
|
|
44
|
+
max_height: Schema.Number,
|
|
45
|
+
}) {
|
|
46
|
+
override get message() {
|
|
47
|
+
return `Image ${this.width}x${this.height} with base64 size ${this.bytes} exceeds configured limits and could not be resized below ${this.max_width}x${this.max_height}/${this.max} bytes`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type Error = ResizerUnavailableError | InvalidDataUrlError | DecodeError | SizeError
|
|
52
|
+
|
|
53
|
+
export interface Interface {
|
|
54
|
+
readonly normalize: (input: SessionV1.FilePart) => Effect.Effect<SessionV1.FilePart, Error>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
|
58
|
+
|
|
59
|
+
const layer = Layer.effect(
|
|
60
|
+
Service,
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
const config = yield* Config.Service
|
|
63
|
+
const loadPhoton = yield* Effect.cached(
|
|
64
|
+
Effect.sync(() => {
|
|
65
|
+
// Patched photon-node reads this during module init so Bun compiled binaries use the embedded wasm path.
|
|
66
|
+
;(globalThis as typeof globalThis & { __OPENCODE_PHOTON_WASM_PATH?: string }).__OPENCODE_PHOTON_WASM_PATH =
|
|
67
|
+
path.isAbsolute(photonWasm) ? photonWasm : fileURLToPath(new URL(photonWasm, import.meta.url))
|
|
68
|
+
}).pipe(
|
|
69
|
+
Effect.andThen(() => Effect.tryPromise(() => import("@silvia-odwyer/photon-node"))),
|
|
70
|
+
Effect.tapError((error) => Effect.logWarning("failed to load photon", { error })),
|
|
71
|
+
Effect.mapError(() => new ResizerUnavailableError()),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const normalize = Effect.fn("Image.normalize")(function* (input: SessionV1.FilePart) {
|
|
76
|
+
const image = (yield* config.get()).attachment?.image
|
|
77
|
+
const info = {
|
|
78
|
+
autoResize: image?.auto_resize ?? AUTO_RESIZE,
|
|
79
|
+
maxWidth: image?.max_width ?? MAX_WIDTH,
|
|
80
|
+
maxHeight: image?.max_height ?? MAX_HEIGHT,
|
|
81
|
+
maxBase64Bytes: image?.max_base64_bytes ?? MAX_BASE64_BYTES,
|
|
82
|
+
}
|
|
83
|
+
if (!input.url.startsWith("data:") || !input.url.includes(";base64,"))
|
|
84
|
+
return yield* new InvalidDataUrlError({ url: input.url })
|
|
85
|
+
|
|
86
|
+
const base64 = input.url.slice(input.url.indexOf(";base64,") + ";base64,".length)
|
|
87
|
+
const bytes = Buffer.byteLength(base64, "utf8")
|
|
88
|
+
|
|
89
|
+
const photon = yield* loadPhoton
|
|
90
|
+
|
|
91
|
+
const decoded = yield* Effect.try({
|
|
92
|
+
try: () => photon.PhotonImage.new_from_byteslice(Buffer.from(base64, "base64")),
|
|
93
|
+
catch: () => new DecodeError(),
|
|
94
|
+
}).pipe(Effect.tapError((error) => Effect.logWarning("failed to decode image", { error })))
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const originalWidth = decoded.get_width()
|
|
98
|
+
const originalHeight = decoded.get_height()
|
|
99
|
+
if (originalWidth <= info.maxWidth && originalHeight <= info.maxHeight && bytes <= info.maxBase64Bytes)
|
|
100
|
+
return input
|
|
101
|
+
if (!info.autoResize)
|
|
102
|
+
return yield* new SizeError({
|
|
103
|
+
bytes,
|
|
104
|
+
max: info.maxBase64Bytes,
|
|
105
|
+
width: originalWidth,
|
|
106
|
+
height: originalHeight,
|
|
107
|
+
max_width: info.maxWidth,
|
|
108
|
+
max_height: info.maxHeight,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const scale = Math.min(1, info.maxWidth / originalWidth, info.maxHeight / originalHeight)
|
|
112
|
+
for (const size of Array.from({ length: 32 }).reduce<Array<{ width: number; height: number }>>((acc) => {
|
|
113
|
+
const previous = acc.at(-1) ?? {
|
|
114
|
+
width: Math.max(1, Math.round(originalWidth * scale)),
|
|
115
|
+
height: Math.max(1, Math.round(originalHeight * scale)),
|
|
116
|
+
}
|
|
117
|
+
const next =
|
|
118
|
+
acc.length === 0
|
|
119
|
+
? previous
|
|
120
|
+
: {
|
|
121
|
+
width: previous.width === 1 ? 1 : Math.max(1, Math.floor(previous.width * 0.75)),
|
|
122
|
+
height: previous.height === 1 ? 1 : Math.max(1, Math.floor(previous.height * 0.75)),
|
|
123
|
+
}
|
|
124
|
+
return acc.some((item) => item.width === next.width && item.height === next.height) ? acc : [...acc, next]
|
|
125
|
+
}, [])) {
|
|
126
|
+
const resized = photon.resize(decoded, size.width, size.height, photon.SamplingFilter.Lanczos3)
|
|
127
|
+
const candidate = [
|
|
128
|
+
{ data: Buffer.from(resized.get_bytes()).toString("base64"), mime: "image/png" },
|
|
129
|
+
...JPEG_QUALITIES.map((quality) => ({
|
|
130
|
+
data: Buffer.from(resized.get_bytes_jpeg(quality)).toString("base64"),
|
|
131
|
+
mime: "image/jpeg",
|
|
132
|
+
})),
|
|
133
|
+
]
|
|
134
|
+
.map((item) => ({ ...item, bytes: Buffer.byteLength(item.data, "utf8") }))
|
|
135
|
+
.find((item) => item.bytes <= info.maxBase64Bytes)
|
|
136
|
+
resized.free()
|
|
137
|
+
|
|
138
|
+
if (candidate) {
|
|
139
|
+
yield* Effect.logInfo("using resized image", {
|
|
140
|
+
from_mime: input.mime,
|
|
141
|
+
to_mime: candidate.mime,
|
|
142
|
+
from: `${originalWidth}x${originalHeight}`,
|
|
143
|
+
to: `${size.width}x${size.height}`,
|
|
144
|
+
})
|
|
145
|
+
return {
|
|
146
|
+
...input,
|
|
147
|
+
mime: candidate.mime,
|
|
148
|
+
url: `data:${candidate.mime};base64,${candidate.data}`,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return yield* new SizeError({
|
|
154
|
+
bytes,
|
|
155
|
+
max: info.maxBase64Bytes,
|
|
156
|
+
width: originalWidth,
|
|
157
|
+
height: originalHeight,
|
|
158
|
+
max_width: info.maxWidth,
|
|
159
|
+
max_height: info.maxHeight,
|
|
160
|
+
})
|
|
161
|
+
} finally {
|
|
162
|
+
decoded.free()
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return Service.of({ normalize })
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Config.node] })
|
|
171
|
+
|
|
172
|
+
export * as Image from "./image"
|