From 1f61eb5ca951174456ddac0d8cb8153417c04e44 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:49:23 +1000 Subject: [PATCH] refactor(frontend): enforce package boundaries (#43177) --- bun.lock | 6 +- .../performance/timeline-stability/fixture.ts | 2 +- .../session-timeline-benchmark.fixture.ts | 2 +- .../timeline/timeline-test-helpers.ts | 2 +- .../unit/timeline-test-helpers.test.ts | 2 +- .../regression/cross-server-tab-close.spec.ts | 2 +- .../file-browser-sidebar-tab-switch.spec.ts | 2 +- .../open-file-expand-folder.spec.ts | 2 +- .../prompt-input-v2-command-draft.spec.ts | 2 +- .../regression/prompt-thinking-level.spec.ts | 2 +- .../remote-session-settings.spec.ts | 2 +- .../e2e/regression/remote-tab-busy.spec.ts | 2 +- .../e2e/regression/review-image-flash.spec.ts | 2 +- .../regression/review-line-comment.spec.ts | 2 +- .../e2e/regression/review-open-file.spec.ts | 2 +- .../review-state-persistence.spec.ts | 2 +- .../e2e/regression/review-tab-switch.spec.ts | 2 +- .../regression/session-request-docks.spec.ts | 2 +- .../session-timeline-history-root.spec.ts | 2 +- .../subagent-child-navigation.spec.ts | 2 +- .../regression/tab-navigate-mousedown.spec.ts | 2 +- .../terminal-composer-focus.spec.ts | 2 +- .../regression/terminal-tab-switch.spec.ts | 2 +- .../app/e2e/smoke/session-timeline.spec.ts | 2 +- packages/app/e2e/utils/waits.ts | 2 +- packages/app/package.json | 1 - .../app/src/components/command-palette.ts | 2 +- .../components/dialog-command-palette-v2.tsx | 2 +- packages/app/src/components/dialog-fork.tsx | 2 +- .../components/dialog-select-directory-v2.tsx | 2 +- .../app/src/components/dialog-select-file.tsx | 2 +- .../src/components/directory-picker-domain.ts | 2 +- packages/app/src/components/edit-project.ts | 2 +- .../prompt-input/build-prompt-request.ts | 2 +- .../components/prompt-input/submit.test.ts | 2 +- .../app/src/components/prompt-input/submit.ts | 4 +- .../components/prompt-workspace-selector.tsx | 2 +- .../src/components/session-workspace-menu.tsx | 2 +- .../session/session-context-tab.tsx | 2 +- .../components/session/session-new-view.tsx | 2 +- .../session/session-sortable-tab.tsx | 2 +- .../src/components/settings-v2/workspaces.tsx | 2 +- .../app/src/components/titlebar-tab-strip.tsx | 2 +- packages/app/src/context/comments.tsx | 2 +- packages/app/src/context/file.tsx | 4 +- .../src/context/global-sync/bootstrap.test.ts | 1 - .../app/src/context/global-sync/bootstrap.ts | 4 +- packages/app/src/context/global-sync/types.ts | 5 +- packages/app/src/context/global-sync/utils.ts | 3 +- packages/app/src/context/local.tsx | 2 +- packages/app/src/context/notification.tsx | 1 - .../context/permission-auto-respond.test.ts | 2 +- .../src/context/permission-auto-respond.ts | 2 +- packages/app/src/context/prompt-state.ts | 2 +- packages/app/src/context/prompt.tsx | 2 +- packages/app/src/context/server-sync.tsx | 2 +- packages/app/src/context/terminal.test.ts | 2 +- packages/app/src/context/terminal.tsx | 2 +- .../app/src/hooks/provider-catalog.test.ts | 4 +- packages/app/src/hooks/provider-catalog.ts | 8 +- packages/app/src/pages/layout/helpers.ts | 2 +- packages/app/src/pages/session.tsx | 6 +- .../composer/session-composer-controls.ts | 1 - packages/app/src/pages/session/file-tabs.tsx | 2 +- .../app/src/pages/session/session-layout.ts | 2 +- .../session/timeline/current-message.tsx | 12 +- .../session/timeline/message-timeline.tsx | 2 +- .../pages/session/v2/session-file-list-v2.tsx | 2 +- packages/app/src/types.ts | 81 +++++++++- packages/app/src/utils/base64.ts | 2 +- packages/app/src/utils/persist.ts | 2 +- packages/app/src/utils/session-message.ts | 2 +- packages/app/src/utils/session-route.ts | 2 +- packages/app/tsconfig.json | 3 +- .../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz | Bin 101030 -> 0 bytes packages/core/src/util/binary.ts | 41 ----- packages/core/src/util/retry.ts | 42 ----- .../enterprise/src/routes/share/[shareID].tsx | 2 +- packages/session-ui/package.json | 3 +- .../session-ui/src/components/file-media.tsx | 4 +- packages/session-ui/src/components/file.tsx | 2 +- .../src/components/line-comment.tsx | 2 +- .../src/components/markdown-cache.tsx | 2 +- .../session-ui/src/components/markdown.tsx | 2 +- .../src/components/message-file.test.ts | 2 +- .../session-ui/src/components/message-file.ts | 4 +- .../session-ui/src/components/message-nav.tsx | 2 +- .../src/components/message-part.tsx | 34 ++-- .../src/components/part-default-open.test.ts | 4 +- .../src/components/part-default-open.ts | 4 +- .../session-ui/src/components/session-diff.ts | 5 +- .../src/components/session-retry.tsx | 2 +- .../src/components/session-review.tsx | 12 +- .../src/components/session-turn.tsx | 22 +-- .../timeline-playground.stories.tsx | 2 +- packages/session-ui/src/context/data.tsx | 12 +- packages/session-ui/src/pierre/media.ts | 4 +- packages/session-ui/src/presentation.ts | 151 ++++++++++++++++++ .../src/v2/components/comment-card-v2.tsx | 2 +- .../src/v2/components/prompt-input/types.ts | 2 +- .../session-review-file-preview-v2.tsx | 8 +- packages/session-ui/tsconfig.json | 3 +- packages/util/src/binary.test.ts | 19 +++ packages/util/src/binary.ts | 30 ++++ packages/util/src/encode.test.ts | 27 ++++ .../{core/src/util => util/src}/encode.ts | 19 +-- packages/util/src/path.test.ts | 25 +++ packages/{core/src/util => util/src}/path.ts | 12 +- packages/util/src/retry.test.ts | 44 +++++ packages/util/src/retry.ts | 45 ++++++ turbo.json | 6 - 111 files changed, 579 insertions(+), 283 deletions(-) delete mode 100644 packages/app/vendor/opencode-ai-sdk-1.18.8-dev.tgz delete mode 100644 packages/core/src/util/binary.ts delete mode 100644 packages/core/src/util/retry.ts create mode 100644 packages/session-ui/src/presentation.ts create mode 100644 packages/util/src/binary.test.ts create mode 100644 packages/util/src/binary.ts create mode 100644 packages/util/src/encode.test.ts rename packages/{core/src/util => util/src}/encode.ts (69%) create mode 100644 packages/util/src/path.test.ts rename packages/{core/src/util => util/src}/path.ts (73%) create mode 100644 packages/util/src/retry.test.ts create mode 100644 packages/util/src/retry.ts diff --git a/bun.lock b/bun.lock index b6f17f5c9e0..04c79c171b7 100644 --- a/bun.lock +++ b/bun.lock @@ -62,7 +62,6 @@ "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", "@opencode-ai/client": "workspace:*", - "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -685,9 +684,8 @@ "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/client": "workspace:*", - "@opencode-ai/core": "workspace:*", - "@opencode-ai/sdk": "file:../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz", "@opencode-ai/ui": "workspace:*", + "@opencode-ai/util": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/stream": "catalog:", "@solid-primitives/event-listener": "2.4.5", @@ -6369,8 +6367,6 @@ "@opencode-ai/desktop/typescript": ["typescript@5.6.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="], - "@opencode-ai/session-ui/@opencode-ai/sdk": ["@opencode-ai/sdk@../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-C2nfk4x0sPINwE5V6DPkFSuH3PkUmKPWHPzxpXC1j+3Ui5hslLCWJbkk8WcOG1Lyt3C0+yp4ea64v/kmtYCO4w=="], - "@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="], "@opencode-ai/storybook/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], diff --git a/packages/app/e2e/performance/timeline-stability/fixture.ts b/packages/app/e2e/performance/timeline-stability/fixture.ts index 9dcef9198e9..4a12a558ca0 100644 --- a/packages/app/e2e/performance/timeline-stability/fixture.ts +++ b/packages/app/e2e/performance/timeline-stability/fixture.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import type { JsonValue, OpenCodeEvent, diff --git a/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts b/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts index 49a34a2a69c..4a047b42997 100644 --- a/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts +++ b/packages/app/e2e/performance/timeline/session-timeline-benchmark.fixture.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import type { JsonValue, OpenCodeEvent, SessionMessageAssistant, SessionMessageInfo } from "@opencode-ai/client/promise" import type { Page } from "@playwright/test" import { mockOpenCodeServer } from "../../utils/mock-server" diff --git a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts index fc8c75a4530..1e21c20322f 100644 --- a/packages/app/e2e/performance/timeline/timeline-test-helpers.ts +++ b/packages/app/e2e/performance/timeline/timeline-test-helpers.ts @@ -1,5 +1,5 @@ import type { Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { mockOpenCodeServer } from "../../utils/mock-server" import { fixture, pageMessages } from "./session-timeline-stress.fixture" diff --git a/packages/app/e2e/performance/unit/timeline-test-helpers.test.ts b/packages/app/e2e/performance/unit/timeline-test-helpers.test.ts index 98b661d91bb..fe607275f58 100644 --- a/packages/app/e2e/performance/unit/timeline-test-helpers.test.ts +++ b/packages/app/e2e/performance/unit/timeline-test-helpers.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { fixture } from "../timeline/session-timeline-stress.fixture" import { stressSessionHref } from "../timeline/timeline-test-helpers" diff --git a/packages/app/e2e/regression/cross-server-tab-close.spec.ts b/packages/app/e2e/regression/cross-server-tab-close.spec.ts index 9505991fa83..d8ecab26282 100644 --- a/packages/app/e2e/regression/cross-server-tab-close.spec.ts +++ b/packages/app/e2e/regression/cross-server-tab-close.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page, type Route } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { currentSession } from "../utils/mock-server" import { installSseTransport } from "../utils/sse-transport" diff --git a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts index 0453de5e756..f318037373b 100644 --- a/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts +++ b/packages/app/e2e/regression/file-browser-sidebar-tab-switch.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/open-file-expand-folder.spec.ts b/packages/app/e2e/regression/open-file-expand-folder.spec.ts index ddc87a4d183..d60e2f055a1 100644 --- a/packages/app/e2e/regression/open-file-expand-folder.spec.ts +++ b/packages/app/e2e/regression/open-file-expand-folder.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/prompt-input-v2-command-draft.spec.ts b/packages/app/e2e/regression/prompt-input-v2-command-draft.spec.ts index 1dff6fe134f..f614656ef2b 100644 --- a/packages/app/e2e/regression/prompt-input-v2-command-draft.spec.ts +++ b/packages/app/e2e/regression/prompt-input-v2-command-draft.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible } from "../utils/waits" diff --git a/packages/app/e2e/regression/prompt-thinking-level.spec.ts b/packages/app/e2e/regression/prompt-thinking-level.spec.ts index 1d5b254e84b..3cd9c45996a 100644 --- a/packages/app/e2e/regression/prompt-thinking-level.spec.ts +++ b/packages/app/e2e/regression/prompt-thinking-level.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible } from "../utils/waits" diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index a99fa833657..c3e9fe4fb29 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page, type Route } from "@playwright/test" import { installSseTransport } from "../utils/sse-transport" import { currentSession } from "../utils/mock-server" diff --git a/packages/app/e2e/regression/remote-tab-busy.spec.ts b/packages/app/e2e/regression/remote-tab-busy.spec.ts index 871bbfca15c..9afc695eacb 100644 --- a/packages/app/e2e/regression/remote-tab-busy.spec.ts +++ b/packages/app/e2e/regression/remote-tab-busy.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page, type Route } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { currentSession } from "../utils/mock-server" const serverA = "http://127.0.0.1:4096" diff --git a/packages/app/e2e/regression/review-image-flash.spec.ts b/packages/app/e2e/regression/review-image-flash.spec.ts index eafeae24f0e..86230e0d62d 100644 --- a/packages/app/e2e/regression/review-image-flash.spec.ts +++ b/packages/app/e2e/regression/review-image-flash.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible, expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/review-line-comment.spec.ts b/packages/app/e2e/regression/review-line-comment.spec.ts index 662e1e4a19f..e1589870b2f 100644 --- a/packages/app/e2e/regression/review-line-comment.spec.ts +++ b/packages/app/e2e/regression/review-line-comment.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible, expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/review-open-file.spec.ts b/packages/app/e2e/regression/review-open-file.spec.ts index 4bd09ea9ce2..7cc47f356c6 100644 --- a/packages/app/e2e/regression/review-open-file.spec.ts +++ b/packages/app/e2e/regression/review-open-file.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/review-state-persistence.spec.ts b/packages/app/e2e/regression/review-state-persistence.spec.ts index 4d72172d25f..84b7254bc84 100644 --- a/packages/app/e2e/regression/review-state-persistence.spec.ts +++ b/packages/app/e2e/regression/review-state-persistence.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/review-tab-switch.spec.ts b/packages/app/e2e/regression/review-tab-switch.spec.ts index 6d248e70b4b..29b8e260d20 100644 --- a/packages/app/e2e/regression/review-tab-switch.spec.ts +++ b/packages/app/e2e/regression/review-tab-switch.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectAppVisible, expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/session-request-docks.spec.ts b/packages/app/e2e/regression/session-request-docks.spec.ts index 63f9e1b27be..356c0b7d466 100644 --- a/packages/app/e2e/regression/session-request-docks.spec.ts +++ b/packages/app/e2e/regression/session-request-docks.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { installSseTransport } from "../utils/sse-transport" diff --git a/packages/app/e2e/regression/session-timeline-history-root.spec.ts b/packages/app/e2e/regression/session-timeline-history-root.spec.ts index ce6b63a9987..86a5175eb8c 100644 --- a/packages/app/e2e/regression/session-timeline-history-root.spec.ts +++ b/packages/app/e2e/regression/session-timeline-history-root.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import type { SessionMessageAssistant } from "@opencode-ai/client/promise" import { expect, test, type Page } from "@playwright/test" import { diff --git a/packages/app/e2e/regression/subagent-child-navigation.spec.ts b/packages/app/e2e/regression/subagent-child-navigation.spec.ts index 12dc41f41d2..a20a7c28876 100644 --- a/packages/app/e2e/regression/subagent-child-navigation.spec.ts +++ b/packages/app/e2e/regression/subagent-child-navigation.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import type { OpenCodeEvent, SessionMessageInfo } from "@opencode-ai/client/promise" import { expect, test, type Page } from "@playwright/test" import { currentSession, mockOpenCodeServer } from "../utils/mock-server" diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index 257d5dc1ca9..06b7ab502ee 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page, type Route } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { currentSession } from "../utils/mock-server" const server = "http://127.0.0.1:4096" diff --git a/packages/app/e2e/regression/terminal-composer-focus.spec.ts b/packages/app/e2e/regression/terminal-composer-focus.spec.ts index 5560c6cee99..51b17180320 100644 --- a/packages/app/e2e/regression/terminal-composer-focus.spec.ts +++ b/packages/app/e2e/regression/terminal-composer-focus.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode, checksum } from "@opencode-ai/core/util/encode" +import { base64Encode, checksum } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/regression/terminal-tab-switch.spec.ts b/packages/app/e2e/regression/terminal-tab-switch.spec.ts index 0d8cf22509b..96331f2a3e1 100644 --- a/packages/app/e2e/regression/terminal-tab-switch.spec.ts +++ b/packages/app/e2e/regression/terminal-tab-switch.spec.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { expect, test, type Page } from "@playwright/test" import { mockOpenCodeServer } from "../utils/mock-server" import { expectSessionTitle } from "../utils/waits" diff --git a/packages/app/e2e/smoke/session-timeline.spec.ts b/packages/app/e2e/smoke/session-timeline.spec.ts index 67090c9095a..f4371a305da 100644 --- a/packages/app/e2e/smoke/session-timeline.spec.ts +++ b/packages/app/e2e/smoke/session-timeline.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { fixture, pageMessages } from "./session-timeline.fixture" import { trackPageErrors, expectNoSmokeErrors } from "../utils/errors" import { mockOpenCodeServer } from "../utils/mock-server" diff --git a/packages/app/e2e/utils/waits.ts b/packages/app/e2e/utils/waits.ts index 01e4f0a7929..814386f7578 100644 --- a/packages/app/e2e/utils/waits.ts +++ b/packages/app/e2e/utils/waits.ts @@ -1,5 +1,5 @@ import { expect, type Locator, type Page } from "@playwright/test" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" export const APP_READY_TIMEOUT = 30_000 diff --git a/packages/app/package.json b/packages/app/package.json index 2e17fc963f6..3497fb83a79 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -56,7 +56,6 @@ "@dnd-kit/solid": "0.5.0", "@kobalte/core": "catalog:", "@opencode-ai/client": "workspace:*", - "@opencode-ai/core": "workspace:*", "@opencode-ai/schema": "workspace:*", "@opencode-ai/session-ui": "workspace:*", "@opencode-ai/ui": "workspace:*", diff --git a/packages/app/src/components/command-palette.ts b/packages/app/src/components/command-palette.ts index 627f2dd5017..1f51bf44553 100644 --- a/packages/app/src/components/command-palette.ts +++ b/packages/app/src/components/command-palette.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import type { Project } from "@/types" import type { SessionInfo } from "@opencode-ai/client/promise" import { useDialog } from "@opencode-ai/ui/context/dialog" diff --git a/packages/app/src/components/dialog-command-palette-v2.tsx b/packages/app/src/components/dialog-command-palette-v2.tsx index 2470989f27f..2756c2094dd 100644 --- a/packages/app/src/components/dialog-command-palette-v2.tsx +++ b/packages/app/src/components/dialog-command-palette-v2.tsx @@ -1,4 +1,4 @@ -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { FileIcon } from "@opencode-ai/ui/file-icon" import { ScrollView } from "@opencode-ai/ui/scroll-view" import { Dialog, DialogBody } from "@opencode-ai/ui/v2/dialog-v2" diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx index b82e613ef06..2823db5e0f5 100644 --- a/packages/app/src/components/dialog-fork.tsx +++ b/packages/app/src/components/dialog-fork.tsx @@ -8,7 +8,7 @@ import { List } from "@opencode-ai/ui/list" import { showToast } from "@/utils/toast" import { useLanguage } from "@/context/language" import { useServerSDK } from "@/context/server-sdk" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { extractPromptComments, extractPromptFromMessage } from "@/utils/prompt" import { useWorkspaceLocation } from "@/context/location" import { useServer } from "@/context/server" diff --git a/packages/app/src/components/dialog-select-directory-v2.tsx b/packages/app/src/components/dialog-select-directory-v2.tsx index d3a57c6e6b7..07c958058ba 100644 --- a/packages/app/src/components/dialog-select-directory-v2.tsx +++ b/packages/app/src/components/dialog-select-directory-v2.tsx @@ -29,7 +29,7 @@ import { } from "./directory-picker-domain" import "./dialog-select-directory-v2.css" import { DividerV2 } from "@opencode-ai/ui/v2/divider-v2" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" interface DialogSelectDirectoryV2Props { title?: string diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx index c389a4e591c..a0b610d972b 100644 --- a/packages/app/src/components/dialog-select-file.tsx +++ b/packages/app/src/components/dialog-select-file.tsx @@ -3,7 +3,7 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import { Icon } from "@opencode-ai/ui/icon" import { Keybind } from "@opencode-ai/ui/keybind" import { List } from "@opencode-ai/ui/list" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createMemo, createSignal, lazy, Match, Show, Switch } from "solid-js" import { formatKeybind } from "@/context/command" import { useServerSDK } from "@/context/server-sdk" diff --git a/packages/app/src/components/directory-picker-domain.ts b/packages/app/src/components/directory-picker-domain.ts index dfd8ee74add..985c3cb5df9 100644 --- a/packages/app/src/components/directory-picker-domain.ts +++ b/packages/app/src/components/directory-picker-domain.ts @@ -245,7 +245,7 @@ export function nativePickerPath(path: string) { if (/^[A-Za-z]:\//.test(value) || value.startsWith("//")) return value.replaceAll("/", "\\") return value } -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import fuzzysort from "fuzzysort" import { ServerSDK } from "@/context/server-sdk" diff --git a/packages/app/src/components/edit-project.ts b/packages/app/src/components/edit-project.ts index 793b1b36c66..397b9e7be97 100644 --- a/packages/app/src/components/edit-project.ts +++ b/packages/app/src/components/edit-project.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useMutation } from "@tanstack/solid-query" import { normalizeProjectInfo } from "@/context/global-sync/utils" diff --git a/packages/app/src/components/prompt-input/build-prompt-request.ts b/packages/app/src/components/prompt-input/build-prompt-request.ts index 255b38a68f6..cc3c80a2664 100644 --- a/packages/app/src/components/prompt-input/build-prompt-request.ts +++ b/packages/app/src/components/prompt-input/build-prompt-request.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import type { FileSelection } from "@/context/file" import { encodeFilePath } from "@/context/file/path" import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt" diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts index b610c843ac2..5397816afcb 100644 --- a/packages/app/src/components/prompt-input/submit.test.ts +++ b/packages/app/src/components/prompt-input/submit.test.ts @@ -173,7 +173,7 @@ beforeAll(async () => { showToast: () => 0, })) - mock.module("@opencode-ai/core/util/encode", () => ({ + mock.module("@opencode-ai/util/encode", () => ({ base64Decode: (value: string) => value, base64Encode: (value: string) => value, checksum: (value: string) => value, diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts index c93aafb6c5d..ecb9c2bfc82 100644 --- a/packages/app/src/components/prompt-input/submit.ts +++ b/packages/app/src/components/prompt-input/submit.ts @@ -1,6 +1,6 @@ import type { Data } from "@opencode-ai/client/solid" import { showToast } from "@/utils/toast" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { useNavigate, useParams, useSearchParams } from "@solidjs/router" import { startTransition, type Accessor } from "solid-js" import { useTabs } from "@/context/tabs" @@ -12,7 +12,7 @@ import { type ContextItem, type ImageAttachmentPart, type Prompt, type usePrompt import { useWorkspaceLocation } from "@/context/location" import { useServerSDK, type ServerSDK } from "@/context/server-sdk" import { Identifier } from "@/utils/id" -import { getDirectory } from "@opencode-ai/core/util/path" +import { getDirectory } from "@opencode-ai/util/path" import { buildPromptRequest } from "./build-prompt-request" import { setCursorPosition } from "./editor-dom" import { formatServerError } from "@/utils/server-errors" diff --git a/packages/app/src/components/prompt-workspace-selector.tsx b/packages/app/src/components/prompt-workspace-selector.tsx index a956c1087ef..fe9ae4f7394 100644 --- a/packages/app/src/components/prompt-workspace-selector.tsx +++ b/packages/app/src/components/prompt-workspace-selector.tsx @@ -2,7 +2,7 @@ import { createMemo, createSignal, For, Show } from "solid-js" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { Icon } from "@opencode-ai/ui/v2/icon" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { useLanguage } from "@/context/language" import { sameDirectory } from "@/utils/workspace" diff --git a/packages/app/src/components/session-workspace-menu.tsx b/packages/app/src/components/session-workspace-menu.tsx index 5aa792b4ce9..7791ac261ab 100644 --- a/packages/app/src/components/session-workspace-menu.tsx +++ b/packages/app/src/components/session-workspace-menu.tsx @@ -1,6 +1,6 @@ import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { Icon } from "@opencode-ai/ui/v2/icon" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createStore } from "solid-js/store" import { createSignal, For, Show, type ComponentProps, type JSX } from "solid-js" import type { Project } from "@/types" diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx index d25e9cda5ed..c1ed1e45be6 100644 --- a/packages/app/src/components/session/session-context-tab.tsx +++ b/packages/app/src/components/session/session-context-tab.tsx @@ -1,7 +1,7 @@ import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js" import type { JSX } from "solid-js" import { useData } from "@/context/server" -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { same } from "@/utils/same" import { Icon } from "@opencode-ai/ui/icon" import { Button } from "@opencode-ai/ui/button" diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx index acaca950c9d..c8bae495cdf 100644 --- a/packages/app/src/components/session/session-new-view.tsx +++ b/packages/app/src/components/session/session-new-view.tsx @@ -5,7 +5,7 @@ import { useLanguage } from "@/context/language" import { useData } from "@/context/server" import { Icon } from "@opencode-ai/ui/icon" import { Mark } from "@opencode-ai/ui/logo" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" const MAIN_WORKTREE = "main" const CREATE_WORKTREE = "create" diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index e8e0934b7c2..dabd1a5358b 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -1,7 +1,7 @@ import { Show } from "solid-js" import type { JSX } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" export function FileVisual(props: { path: string; active?: boolean; temporary?: boolean }): JSX.Element { return ( diff --git a/packages/app/src/components/settings-v2/workspaces.tsx b/packages/app/src/components/settings-v2/workspaces.tsx index 605d4885f73..edcc6dbc4fb 100644 --- a/packages/app/src/components/settings-v2/workspaces.tsx +++ b/packages/app/src/components/settings-v2/workspaces.tsx @@ -10,7 +10,7 @@ import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { useDialog } from "@opencode-ai/ui/context/dialog" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { useLanguage } from "@/context/language" import { useServerSDK } from "@/context/server-sdk" import { useData } from "@/context/server" diff --git a/packages/app/src/components/titlebar-tab-strip.tsx b/packages/app/src/components/titlebar-tab-strip.tsx index fd48ee2cfb9..555bc83633d 100644 --- a/packages/app/src/components/titlebar-tab-strip.tsx +++ b/packages/app/src/components/titlebar-tab-strip.tsx @@ -15,7 +15,7 @@ import { useLanguage } from "@/context/language" import { useCommand } from "@/context/command" import { useTabs } from "@/context/tabs" import { createTabPromptState } from "@/context/prompt" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { showToast } from "@/utils/toast" import { canStartTabDrag, isTabCloseTarget } from "./titlebar-tab-gesture" import { adjacentTabKey, mergeVisibleTabOrder } from "./titlebar-tab-order" diff --git a/packages/app/src/context/comments.tsx b/packages/app/src/context/comments.tsx index f2f23342f51..06ccd985f8a 100644 --- a/packages/app/src/context/comments.tsx +++ b/packages/app/src/context/comments.tsx @@ -2,7 +2,7 @@ import { batch, createMemo, createRoot, onCleanup } from "solid-js" import { createStore, reconcile, type SetStoreFunction, type Store } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { useParams } from "@solidjs/router" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { Persist, persisted } from "@/utils/persist" import { useServerSDK } from "./server-sdk" import type { ServerScope } from "@/utils/server-scope" diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx index a9b083e2dfb..52768f840d6 100644 --- a/packages/app/src/context/file.tsx +++ b/packages/app/src/context/file.tsx @@ -3,8 +3,8 @@ import { createStore, produce, reconcile } from "solid-js/store" import { createSimpleContext } from "@opencode-ai/ui/context" import { showToast } from "@/utils/toast" import { useParams } from "@solidjs/router" -import { base64Encode } from "@opencode-ai/core/util/encode" -import { getFilename } from "@opencode-ai/core/util/path" +import { base64Encode } from "@opencode-ai/util/encode" +import { getFilename } from "@opencode-ai/util/path" import { useWorkspaceLocation } from "./location" import { useLanguage } from "@/context/language" import { useLayout } from "@/context/layout" diff --git a/packages/app/src/context/global-sync/bootstrap.test.ts b/packages/app/src/context/global-sync/bootstrap.test.ts index 435902eb53c..d3d553ff25c 100644 --- a/packages/app/src/context/global-sync/bootstrap.test.ts +++ b/packages/app/src/context/global-sync/bootstrap.test.ts @@ -1,6 +1,5 @@ import { describe, expect, test } from "bun:test" import { QueryClient } from "@tanstack/solid-query" -import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import { loadPathQuery, loadProjectsQuery } from "./bootstrap" import { ServerScope } from "@/utils/server-scope" import type { ServerApi } from "@/utils/server" diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts index b7cf1b04359..9e7a1cf6e1d 100644 --- a/packages/app/src/context/global-sync/bootstrap.ts +++ b/packages/app/src/context/global-sync/bootstrap.ts @@ -7,8 +7,8 @@ import type { ProjectListOutput, } from "@opencode-ai/client/promise" import { showToast } from "@/utils/toast" -import { getFilename } from "@opencode-ai/core/util/path" -import { retry } from "@opencode-ai/core/util/retry" +import { getFilename } from "@opencode-ai/util/path" +import { retry } from "@opencode-ai/util/retry" import { reconcile, type SetStoreFunction, type Store } from "solid-js/store" import type { State } from "./types" import { cmp, normalizeProjectInfo } from "./utils" diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index c3f6b28f95d..0e543209c95 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -1,6 +1,5 @@ -import type { Agent, Config, LspStatus, Path, VcsInfo } from "@/types" +import type { Agent, Config, LspStatus, Path, ProviderListResponse, VcsInfo } from "@/types" import type { ReferenceInfo } from "@opencode-ai/client/promise" -import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" import type { CommandInfo, McpResource, McpServer } from "@opencode-ai/client/promise" import type { Accessor } from "solid-js" import type { SetStoreFunction, Store } from "solid-js/store" @@ -25,7 +24,7 @@ export type State = { projectMeta: ProjectMeta | undefined icon: string | undefined provider_ready: boolean - provider: NormalizedProviderListResponse + provider: ProviderListResponse config: Config path: Path mcp_ready: boolean diff --git a/packages/app/src/context/global-sync/utils.ts b/packages/app/src/context/global-sync/utils.ts index 1c33c297571..f783e1b0a10 100644 --- a/packages/app/src/context/global-sync/utils.ts +++ b/packages/app/src/context/global-sync/utils.ts @@ -1,7 +1,6 @@ import type { AgentListOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise" import type { Agent, Project, Provider, ProviderListResponse } from "@/types" import type { Project as CurrentProject } from "@opencode-ai/client/promise" -import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" export { pathKey as directoryKey, type PathKey as DirectoryKey } from "@/utils/path-key" export const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0) @@ -33,7 +32,7 @@ export function normalizeAgentList(input: AgentListOutput["data"] | Agent[]): Ag export function normalizeProviderList( providers: ProviderListOutput["data"] | ProviderListResponse, models?: ModelListOutput["data"], -): NormalizedProviderListResponse { +): ProviderListResponse { if (!Array.isArray(providers)) { return providers } diff --git a/packages/app/src/context/local.tsx b/packages/app/src/context/local.tsx index 99c4ec20a8f..d9662b037a2 100644 --- a/packages/app/src/context/local.tsx +++ b/packages/app/src/context/local.tsx @@ -1,5 +1,5 @@ import { createSimpleContext } from "@opencode-ai/ui/context" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { useParams } from "@solidjs/router" import { batch, createEffect, createMemo, startTransition } from "solid-js" import { createStore } from "solid-js/store" diff --git a/packages/app/src/context/notification.tsx b/packages/app/src/context/notification.tsx index 6db7369b596..5ac758d9440 100644 --- a/packages/app/src/context/notification.tsx +++ b/packages/app/src/context/notification.tsx @@ -7,7 +7,6 @@ import type { OpenCodeEvent } from "@opencode-ai/client/promise" import { usePlatform } from "@/context/platform" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" -import { base64Encode } from "@opencode-ai/core/util/encode" import { decode64 } from "@/utils/base64" import { Persist, persisted } from "@/utils/persist" import { playSoundById } from "@/utils/sound" diff --git a/packages/app/src/context/permission-auto-respond.test.ts b/packages/app/src/context/permission-auto-respond.test.ts index 1cf50090df6..e89b2849946 100644 --- a/packages/app/src/context/permission-auto-respond.test.ts +++ b/packages/app/src/context/permission-auto-respond.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test" import type { PermissionRequest, SessionInfo } from "@opencode-ai/client/promise" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { autoRespondsPermission, isDirectoryAutoAccepting, diff --git a/packages/app/src/context/permission-auto-respond.ts b/packages/app/src/context/permission-auto-respond.ts index 7828de607e2..15192dbd1c0 100644 --- a/packages/app/src/context/permission-auto-respond.ts +++ b/packages/app/src/context/permission-auto-respond.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" export function acceptKey(sessionID: string, directory?: string) { if (!directory) return sessionID diff --git a/packages/app/src/context/prompt-state.ts b/packages/app/src/context/prompt-state.ts index 69c52a59607..200b93b99a5 100644 --- a/packages/app/src/context/prompt-state.ts +++ b/packages/app/src/context/prompt-state.ts @@ -1,4 +1,4 @@ -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { batch, createMemo, type Accessor } from "solid-js" import { createStore, type SetStoreFunction } from "solid-js/store" import type { FileSelection } from "@/context/file" diff --git a/packages/app/src/context/prompt.tsx b/packages/app/src/context/prompt.tsx index a0d57136d9d..98e3ee5581d 100644 --- a/packages/app/src/context/prompt.tsx +++ b/packages/app/src/context/prompt.tsx @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { createSimpleContext } from "@opencode-ai/ui/context" import { useParams, useSearchParams } from "@solidjs/router" import { createMemo, createResource, createRoot, getOwner, onCleanup } from "solid-js" diff --git a/packages/app/src/context/server-sync.tsx b/packages/app/src/context/server-sync.tsx index 473631f9f6b..a1308817466 100644 --- a/packages/app/src/context/server-sync.tsx +++ b/packages/app/src/context/server-sync.tsx @@ -1,6 +1,6 @@ import type { Config, Path, Project, ProviderAuthResponse } from "@/types" import { showToast } from "@/utils/toast" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { getOwner, onCleanup, untrack } from "solid-js" import { createStore } from "solid-js/store" import { useLanguage } from "@/context/language" diff --git a/packages/app/src/context/terminal.test.ts b/packages/app/src/context/terminal.test.ts index b67473ea8af..6a9fe86a70a 100644 --- a/packages/app/src/context/terminal.test.ts +++ b/packages/app/src/context/terminal.test.ts @@ -1,6 +1,6 @@ import { beforeAll, describe, expect, mock, test } from "bun:test" import { ServerScope } from "@/utils/server-scope" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { Persist } from "@/utils/persist" import type { Platform } from "./platform" diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx index cd707cb44a4..d0ee9c51c01 100644 --- a/packages/app/src/context/terminal.tsx +++ b/packages/app/src/context/terminal.tsx @@ -4,7 +4,7 @@ import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "soli import { useWorkspaceLocation, type LocationContext } from "./location" import type { Platform } from "./platform" import { useServerSDK } from "./server-sdk" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { defaultTitle, titleNumber } from "./terminal-title" import { Persist, persisted, removePersisted } from "@/utils/persist" import { ScopedKey, ServerScope, type ServerScope as ServerScopeValue } from "@/utils/server-scope" diff --git a/packages/app/src/hooks/provider-catalog.test.ts b/packages/app/src/hooks/provider-catalog.test.ts index 04fbe5ab66e..c8122674337 100644 --- a/packages/app/src/hooks/provider-catalog.test.ts +++ b/packages/app/src/hooks/provider-catalog.test.ts @@ -1,8 +1,8 @@ import { expect, test } from "bun:test" -import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" +import type { ProviderListResponse } from "@/types" import { selectProviderCatalog } from "./provider-catalog" -const catalog = (id: string): NormalizedProviderListResponse => ({ +const catalog = (id: string): ProviderListResponse => ({ all: new Map([[id, { id, name: id, source: "api", env: [], options: {}, models: {} }]]), connected: [id], default: { [id]: `${id}-model` }, diff --git a/packages/app/src/hooks/provider-catalog.ts b/packages/app/src/hooks/provider-catalog.ts index 54c061202dc..c40789631e4 100644 --- a/packages/app/src/hooks/provider-catalog.ts +++ b/packages/app/src/hooks/provider-catalog.ts @@ -1,10 +1,10 @@ -import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" +import type { ProviderListResponse } from "@/types" -export const emptyProviderCatalog: NormalizedProviderListResponse = { all: new Map(), connected: [], default: {} } +export const emptyProviderCatalog: ProviderListResponse = { all: new Map(), connected: [], default: {} } type DirectoryCatalog = { ready: boolean - providers: NormalizedProviderListResponse + providers: ProviderListResponse } type ProviderCatalogInput = @@ -17,7 +17,7 @@ type ProviderCatalogInput = explicit: false directory?: string catalog?: DirectoryCatalog - global: NormalizedProviderListResponse + global: ProviderListResponse } export function selectProviderCatalog(input: ProviderCatalogInput) { diff --git a/packages/app/src/pages/layout/helpers.ts b/packages/app/src/pages/layout/helpers.ts index e5ab005fd56..96718526136 100644 --- a/packages/app/src/pages/layout/helpers.ts +++ b/packages/app/src/pages/layout/helpers.ts @@ -1,4 +1,4 @@ -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import type { SessionInfo } from "@opencode-ai/client/promise" import { pathKey } from "@/utils/path-key" import type { ServerConnection } from "@/context/servers" diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index fa73fbcf20d..0b3405996a7 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1,6 +1,6 @@ -import type { FilePart } from "@opencode-ai/sdk/v2" +import type { FilePart } from "@/types" import type { FileDiffInfo, SessionMessageUser } from "@opencode-ai/client/promise" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { useDialog } from "@opencode-ai/ui/context/dialog" import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query" import { @@ -37,7 +37,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { createAutoScroll } from "@opencode-ai/ui/hooks" import { previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge" import { showToast } from "@/utils/toast" -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { containsDirectory, isWorkspaceDirectory } from "@/utils/workspace" import { useLocation, useNavigate, useParams, useSearchParams } from "@solidjs/router" import { NewSessionView, SessionHeader } from "@/components/session" diff --git a/packages/app/src/pages/session/composer/session-composer-controls.ts b/packages/app/src/pages/session/composer/session-composer-controls.ts index 25f6da6a60e..c98c4ecbcef 100644 --- a/packages/app/src/pages/session/composer/session-composer-controls.ts +++ b/packages/app/src/pages/session/composer/session-composer-controls.ts @@ -1,4 +1,3 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" import { useNavigate, useSearchParams } from "@solidjs/router" import { type Accessor, createMemo } from "solid-js" import type { PromptInputControls } from "@/components/prompt-input/contracts" diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx index 5adaa15881e..2abab58d874 100644 --- a/packages/app/src/pages/session/file-tabs.tsx +++ b/packages/app/src/pages/session/file-tabs.tsx @@ -6,7 +6,7 @@ import type { FileSearchHandle } from "@opencode-ai/session-ui/file" import { useFileComponent } from "@opencode-ai/ui/context/file" import { cloneSelectedLineRange, previewSelectedLines } from "@opencode-ai/session-ui/pierre/selection-bridge" import { createLineCommentControllerV2 } from "@opencode-ai/session-ui/v2/line-comment-annotations-v2" -import { sampledChecksum } from "@opencode-ai/core/util/encode" +import { sampledChecksum } from "@opencode-ai/util/encode" import { LineCommentV2OverflowIcon } from "@opencode-ai/ui/v2/line-comment-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import { Tabs } from "@opencode-ai/ui/tabs" diff --git a/packages/app/src/pages/session/session-layout.ts b/packages/app/src/pages/session/session-layout.ts index a8716eae459..791bff39c15 100644 --- a/packages/app/src/pages/session/session-layout.ts +++ b/packages/app/src/pages/session/session-layout.ts @@ -4,7 +4,7 @@ import { useLayout } from "@/context/layout" import { SessionRouteKey, SessionStateKey } from "@/utils/server-scope" import { useWorkspaceLocation } from "@/context/location" import { useServerSDK } from "@/context/server-sdk" -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" export const useSessionKey = () => { const params = useParams() diff --git a/packages/app/src/pages/session/timeline/current-message.tsx b/packages/app/src/pages/session/timeline/current-message.tsx index 030a52aa101..36ad91cdfd6 100644 --- a/packages/app/src/pages/session/timeline/current-message.tsx +++ b/packages/app/src/pages/session/timeline/current-message.tsx @@ -10,7 +10,7 @@ import { partDefaultOpen, type UserActions, } from "@opencode-ai/session-ui/message-part" -import type { ToolPart } from "@opencode-ai/sdk/v2" +import type { ToolPart } from "@/types" import { presentAssistantMessage, presentAssistantContent, @@ -56,9 +56,7 @@ export function CurrentAssistantContent(props: { onContentRendered?: () => void }) { const message = createMemo(() => presentAssistantMessage(props.sessionID, props.parentID, props.message)) - const part = createMemo(() => - presentAssistantContent(props.sessionID, props.message, props.contentID, props.content), - ) + const part = createMemo(() => presentAssistantContent(props.sessionID, props.message, props.contentID, props.content)) return ( {(part) => ( @@ -113,9 +111,5 @@ export function currentPartDefaultOpen( shellExpanded: boolean, editExpanded: boolean, ) { - return partDefaultOpen( - presentAssistantContent(sessionID, message, contentID, content), - shellExpanded, - editExpanded, - ) + return partDefaultOpen(presentAssistantContent(sessionID, message, contentID, content), shellExpanded, editExpanded) } diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx index ec90203967a..5880ccc58df 100644 --- a/packages/app/src/pages/session/timeline/message-timeline.tsx +++ b/packages/app/src/pages/session/timeline/message-timeline.tsx @@ -27,7 +27,7 @@ import { isScrollKeyTarget, scrollKey, scrollKeyOwner, ScrollView } from "@openc import { TextReveal } from "@opencode-ai/ui/text-reveal" import { TextShimmer } from "@opencode-ai/ui/text-shimmer" import type { Project } from "@/types" -import { getFilename } from "@opencode-ai/core/util/path" +import { getFilename } from "@opencode-ai/util/path" import { Popover as KobaltePopover } from "@kobalte/core/popover" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" import { SessionContextUsage } from "@/components/session-context-usage" diff --git a/packages/app/src/pages/session/v2/session-file-list-v2.tsx b/packages/app/src/pages/session/v2/session-file-list-v2.tsx index 16cac2a45d6..5e940f29851 100644 --- a/packages/app/src/pages/session/v2/session-file-list-v2.tsx +++ b/packages/app/src/pages/session/v2/session-file-list-v2.tsx @@ -1,6 +1,6 @@ import { FileIcon } from "@opencode-ai/ui/file-icon" import "@opencode-ai/ui/v2/file-tree-v2.css" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, Show } from "solid-js" import { kindChange, kindLabel, type Kind } from "@/components/file-tree-v2" import { normalizePath } from "@/pages/session/v2/review-diff-kinds" diff --git a/packages/app/src/types.ts b/packages/app/src/types.ts index 8cbd1e6b317..99438fb7c38 100644 --- a/packages/app/src/types.ts +++ b/packages/app/src/types.ts @@ -1,5 +1,4 @@ import type { FileDiffInfo, ProjectListOutput, WorktreeDirectory } from "@opencode-ai/client/promise" -import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context" export type Project = Omit & { worktree: string @@ -257,9 +256,83 @@ export type Agent = { steps?: number } -export type Provider = NormalizedProviderListResponse["all"] extends Map ? Item : never -export type Model = Provider["models"][string] -export type ProviderListResponse = NormalizedProviderListResponse +export type Model = { + id: string + providerID: string + api: { + id: string + url: string + npm: string + } + name: string + family?: string + capabilities: { + temperature: boolean + reasoning: boolean + attachment: boolean + toolcall: boolean + input: { + text: boolean + audio: boolean + image: boolean + video: boolean + pdf: boolean + } + output: { + text: boolean + audio: boolean + image: boolean + video: boolean + pdf: boolean + } + interleaved: boolean | { field: "reasoning" | "reasoning_content" | "reasoning_details" } + } + cost: { + input: number + output: number + cache: { + read: number + write: number + } + tiers?: { + input: number + output: number + cache: { read: number; write: number } + tier: { type: "context"; size: number } + }[] + experimentalOver200K?: { + input: number + output: number + cache: { read: number; write: number } + } + } + limit: { + context: number + input?: number + output: number + } + status: "alpha" | "beta" | "deprecated" | "active" + options: Record + headers: Record + release_date: string + variants?: Record> +} + +export type Provider = { + id: string + name: string + source: "env" | "config" | "custom" | "api" + env: string[] + key?: string + options: Record + models: Record +} + +export type ProviderListResponse = { + all: Map + default: Record + connected: string[] +} export type ProviderAuthResponse = Record diff --git a/packages/app/src/utils/base64.ts b/packages/app/src/utils/base64.ts index 34b904051ca..c1f9d88c6e9 100644 --- a/packages/app/src/utils/base64.ts +++ b/packages/app/src/utils/base64.ts @@ -1,4 +1,4 @@ -import { base64Decode } from "@opencode-ai/core/util/encode" +import { base64Decode } from "@opencode-ai/util/encode" export function decode64(value: string | undefined) { if (value === undefined) return diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index 301177b62db..6983c95fab5 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -1,6 +1,6 @@ import { Platform, usePlatform } from "@/context/platform" import { makePersisted, type AsyncStorage, type SyncStorage } from "@solid-primitives/storage" -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { createResource, type Accessor } from "solid-js" import type { SetStoreFunction, Store } from "solid-js/store" import { pathKey } from "@/utils/path-key" diff --git a/packages/app/src/utils/session-message.ts b/packages/app/src/utils/session-message.ts index db80fba980a..e1a1a1529b1 100644 --- a/packages/app/src/utils/session-message.ts +++ b/packages/app/src/utils/session-message.ts @@ -3,7 +3,7 @@ import type { SessionMessageAssistantTool, SessionMessageUser, } from "@opencode-ai/client/promise" -import type { AssistantMessage, FilePart, Part, ToolPart, UserMessage } from "@opencode-ai/sdk/v2" +import type { AssistantMessage, FilePart, Part, ToolPart, UserMessage } from "@/types" import { Option, Schema } from "effect" import { createCommentMetadata, formatCommentNote, readPromptPresentation } from "./comment-note" diff --git a/packages/app/src/utils/session-route.ts b/packages/app/src/utils/session-route.ts index 9e60c315ed1..cf7d47cea30 100644 --- a/packages/app/src/utils/session-route.ts +++ b/packages/app/src/utils/session-route.ts @@ -1,4 +1,4 @@ -import { base64Encode } from "@opencode-ai/core/util/encode" +import { base64Encode } from "@opencode-ai/util/encode" import { ServerConnection } from "@/context/servers" import { decode64 } from "@/utils/base64" diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index 03256d1ba96..e2a27dd5d8a 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -22,6 +22,5 @@ } }, "include": ["src", "package.json"], - "exclude": ["dist", "ts-dist"], - "references": [{ "path": "../core" }] + "exclude": ["dist", "ts-dist"] } diff --git a/packages/app/vendor/opencode-ai-sdk-1.18.8-dev.tgz b/packages/app/vendor/opencode-ai-sdk-1.18.8-dev.tgz deleted file mode 100644 index 67cb2d7cb2ae0cc14fe963bb5d0256a0e41e075e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101030 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: T[], id: string, compare: (item: T) => string): { found: boolean; index: number } { - let left = 0 - let right = array.length - 1 - - while (left <= right) { - const mid = Math.floor((left + right) / 2) - const midId = compare(array[mid]) - - if (midId === id) { - return { found: true, index: mid } - } else if (midId < id) { - left = mid + 1 - } else { - right = mid - 1 - } - } - - return { found: false, index: left } - } - - export function insert(array: T[], item: T, compare: (item: T) => string): T[] { - const id = compare(item) - let left = 0 - let right = array.length - - while (left < right) { - const mid = Math.floor((left + right) / 2) - const midId = compare(array[mid]) - - if (midId < id) { - left = mid + 1 - } else { - right = mid - } - } - - array.splice(left, 0, item) - return array - } -} diff --git a/packages/core/src/util/retry.ts b/packages/core/src/util/retry.ts deleted file mode 100644 index 831d238005f..00000000000 --- a/packages/core/src/util/retry.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface RetryOptions { - attempts?: number - delay?: number - factor?: number - maxDelay?: number - retryIf?: (error: unknown) => boolean -} - -const TRANSIENT_MESSAGES = [ - "load failed", - "network connection was lost", - "network request failed", - "failed to fetch", - "econnreset", - "econnrefused", - "etimedout", - "socket hang up", -] - -function isTransientError(error: unknown): boolean { - if (!error) return false - // oxlint-disable-next-line no-base-to-string -- error is unknown, intentional coercion for message matching - const message = String(error instanceof Error ? error.message : error).toLowerCase() - return TRANSIENT_MESSAGES.some((m) => message.includes(m)) -} - -export async function retry(fn: () => Promise, options: RetryOptions = {}): Promise { - const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options - - let lastError: unknown - for (let attempt = 0; attempt < attempts; attempt++) { - try { - return await fn() - } catch (error) { - lastError = error - if (attempt === attempts - 1 || !retryIf(error)) throw error - const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay) - await new Promise((resolve) => setTimeout(resolve, wait)) - } - } - throw lastError -} diff --git a/packages/enterprise/src/routes/share/[shareID].tsx b/packages/enterprise/src/routes/share/[shareID].tsx index bd6279cef19..ba124b98b04 100644 --- a/packages/enterprise/src/routes/share/[shareID].tsx +++ b/packages/enterprise/src/routes/share/[shareID].tsx @@ -12,7 +12,7 @@ import { Logo, Mark } from "@opencode-ai/ui/logo" import { IconButton } from "@opencode-ai/ui/icon-button" import { ProviderIcon } from "@opencode-ai/ui/provider-icon" import { iife } from "@opencode-ai/core/util/iife" -import { Binary } from "@opencode-ai/core/util/binary" +import { Binary } from "@opencode-ai/util/binary" import { NamedError } from "@opencode-ai/core/util/error" import { DateTime } from "luxon" import { createStore } from "solid-js/store" diff --git a/packages/session-ui/package.json b/packages/session-ui/package.json index bc97cbcd47b..96aed779c7b 100644 --- a/packages/session-ui/package.json +++ b/packages/session-ui/package.json @@ -40,9 +40,8 @@ "dependencies": { "@kobalte/core": "catalog:", "@opencode-ai/client": "workspace:*", - "@opencode-ai/core": "workspace:*", - "@opencode-ai/sdk": "file:../app/vendor/opencode-ai-sdk-1.18.8-dev.tgz", "@opencode-ai/ui": "workspace:*", + "@opencode-ai/util": "workspace:*", "@pierre/diffs": "catalog:", "@shikijs/stream": "catalog:", "@solid-primitives/event-listener": "2.4.5", diff --git a/packages/session-ui/src/components/file-media.tsx b/packages/session-ui/src/components/file-media.tsx index 4e89970ac5c..07175ba3483 100644 --- a/packages/session-ui/src/components/file-media.tsx +++ b/packages/session-ui/src/components/file-media.tsx @@ -1,4 +1,4 @@ -import type { FileContent } from "@opencode-ai/sdk/v2" +import type { PresentationFileContent } from "../presentation" import { createEffect, createMemo, Match, on, onCleanup, Show, Switch, untrack, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { useI18n } from "@opencode-ai/ui/context/i18n" @@ -18,7 +18,7 @@ export type FileMediaOptions = { before?: unknown after?: unknown deleted?: boolean - readFile?: (path: string) => Promise + readFile?: (path: string) => Promise onLoad?: () => void onError?: (ctx: { kind: "image" | "audio" | "svg" }) => void } diff --git a/packages/session-ui/src/components/file.tsx b/packages/session-ui/src/components/file.tsx index b0cb4e5e2de..704971b014d 100644 --- a/packages/session-ui/src/components/file.tsx +++ b/packages/session-ui/src/components/file.tsx @@ -1,4 +1,4 @@ -import { sampledChecksum } from "@opencode-ai/core/util/encode" +import { sampledChecksum } from "@opencode-ai/util/encode" import { areFilesEqual, areOptionsEqual, diff --git a/packages/session-ui/src/components/line-comment.tsx b/packages/session-ui/src/components/line-comment.tsx index 083734b2b8f..076b6fe8ca2 100644 --- a/packages/session-ui/src/components/line-comment.tsx +++ b/packages/session-ui/src/components/line-comment.tsx @@ -1,5 +1,5 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js" import { Button } from "@opencode-ai/ui/button" import { FileIcon } from "@opencode-ai/ui/file-icon" diff --git a/packages/session-ui/src/components/markdown-cache.tsx b/packages/session-ui/src/components/markdown-cache.tsx index ee9efef0560..80cb3fa9e04 100644 --- a/packages/session-ui/src/components/markdown-cache.tsx +++ b/packages/session-ui/src/components/markdown-cache.tsx @@ -1,4 +1,4 @@ -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import DOMPurify from "dompurify" import { parseMarkdown } from "./markdown-worker" diff --git a/packages/session-ui/src/components/markdown.tsx b/packages/session-ui/src/components/markdown.tsx index 23aa8aa173d..2e12fce1aeb 100644 --- a/packages/session-ui/src/components/markdown.tsx +++ b/packages/session-ui/src/components/markdown.tsx @@ -1,6 +1,6 @@ import { useI18n } from "@opencode-ai/ui/context/i18n" import morphdom from "morphdom" -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { type ComponentProps, createEffect, diff --git a/packages/session-ui/src/components/message-file.test.ts b/packages/session-ui/src/components/message-file.test.ts index 8b6872381ed..080bef3c0f3 100644 --- a/packages/session-ui/src/components/message-file.test.ts +++ b/packages/session-ui/src/components/message-file.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { FilePart } from "@opencode-ai/sdk/v2" +import type { FilePart } from "../presentation" import { attached, inline, kind, typeLabel } from "./message-file" function file(part: Partial = {}): FilePart { diff --git a/packages/session-ui/src/components/message-file.ts b/packages/session-ui/src/components/message-file.ts index 0b0a6fc3b5f..e00bdb847f7 100644 --- a/packages/session-ui/src/components/message-file.ts +++ b/packages/session-ui/src/components/message-file.ts @@ -1,6 +1,6 @@ import { bundledLanguagesInfo } from "shiki" -import { getFilename } from "@opencode-ai/core/util/path" -import type { FilePart } from "@opencode-ai/sdk/v2" +import { getFilename } from "@opencode-ai/util/path" +import type { FilePart } from "../presentation" export function attached(part: FilePart) { return part.url.startsWith("data:") && !inline(part) diff --git a/packages/session-ui/src/components/message-nav.tsx b/packages/session-ui/src/components/message-nav.tsx index 85db1e1bcdb..f971679c192 100644 --- a/packages/session-ui/src/components/message-nav.tsx +++ b/packages/session-ui/src/components/message-nav.tsx @@ -1,4 +1,4 @@ -import { UserMessage } from "@opencode-ai/sdk/v2" +import type { UserMessage } from "../presentation" import { HoverCard } from "@kobalte/core/hover-card" import { ComponentProps, For, Match, Show, createSignal, splitProps, Switch } from "solid-js" import { DiffChanges } from "@opencode-ai/ui/diff-changes" diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index 043654df98f..df468376241 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -16,7 +16,7 @@ import { import { createStore } from "solid-js/store" import stripAnsi from "strip-ansi" import { Dynamic } from "solid-js/web" -import { +import type { AgentPart, AssistantMessage, FilePart, @@ -29,7 +29,7 @@ import { Todo, QuestionAnswer, QuestionInfo, -} from "@opencode-ai/sdk/v2" +} from "../presentation" import { type SessionSummary, useData } from "../context" import { useFileComponent } from "@opencode-ai/ui/context/file" import { useDialog } from "@opencode-ai/ui/context/dialog" @@ -45,10 +45,10 @@ import { Checkbox } from "@opencode-ai/ui/checkbox" import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { Markdown } from "./markdown" import { ImagePreview } from "@opencode-ai/ui/image-preview" -import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { AttachmentCardV2 } from "../v2/components/attachment-card-v2" import { CommentCardV2 } from "../v2/components/comment-card-v2" -import { checksum } from "@opencode-ai/core/util/encode" +import { checksum } from "@opencode-ai/util/encode" import { Tooltip } from "@opencode-ai/ui/tooltip" import { IconButton } from "@opencode-ai/ui/icon-button" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" @@ -358,9 +358,9 @@ function relativizeProjectPath(path: string, directory?: string) { return path.slice(directory.length) } -function getDirectory(path: string | undefined) { +function displayDirectory(path: string | undefined) { const data = useData() - return relativizeProjectPath(_getDirectory(path), data.directory) + return relativizeProjectPath(getDirectory(path), data.directory) } import type { IconProps } from "@opencode-ai/ui/icon" @@ -888,12 +888,12 @@ function contextToolTrigger(part: ToolPart, i18n: ReturnType) { case "list": return { title: i18n.t("ui.tool.list"), - subtitle: getDirectory(path), + subtitle: displayDirectory(path), } case "glob": return { title: i18n.t("ui.tool.glob"), - subtitle: getDirectory(path), + subtitle: displayDirectory(path), args: [...(pattern ? ["pattern=" + pattern] : []), ...(matches ? [matches] : [])], } case "grep": { @@ -903,7 +903,7 @@ function contextToolTrigger(part: ToolPart, i18n: ReturnType) { if (matches) args.push(matches) return { title: i18n.t("ui.tool.grep"), - subtitle: getDirectory(path), + subtitle: displayDirectory(path), args, } } @@ -1534,7 +1534,7 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre
- {`\u202A${getDirectory(props.path)}\u202C`} + {`\u202A${displayDirectory(props.path)}\u202C`} {getFilename(props.path)}
@@ -1863,7 +1863,7 @@ ToolRegistry.register({
@@ -2344,7 +2344,7 @@ ToolRegistry.register({
- {getDirectory(props.input.filePath!)} + {displayDirectory(props.input.filePath!)}
@@ -2411,7 +2411,7 @@ ToolRegistry.register({
- {getDirectory(props.input.filePath!)} + {displayDirectory(props.input.filePath!)}
@@ -2520,7 +2520,7 @@ ToolRegistry.register({
- {`\u202A${getDirectory(file.relativePath)}\u202C`} + {`\u202A${displayDirectory(file.relativePath)}\u202C`} {getFilename(file.relativePath)}
@@ -2593,7 +2593,7 @@ ToolRegistry.register({
- {getDirectory(single()!.relativePath)} + {displayDirectory(single()!.relativePath)}
diff --git a/packages/session-ui/src/components/part-default-open.test.ts b/packages/session-ui/src/components/part-default-open.test.ts index 0ea671100b9..26a23bf8a2b 100644 --- a/packages/session-ui/src/components/part-default-open.test.ts +++ b/packages/session-ui/src/components/part-default-open.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import type { Part as PartType } from "@opencode-ai/sdk/v2" +import type { Part } from "../presentation" import { partDefaultOpen } from "./part-default-open" describe("partDefaultOpen", () => { @@ -77,7 +77,7 @@ describe("partDefaultOpen", () => { }) }) -function tool(name: string, metadata: Record): PartType { +function tool(name: string, metadata: Record): Part { return { id: `part_${name}`, sessionID: "session", diff --git a/packages/session-ui/src/components/part-default-open.ts b/packages/session-ui/src/components/part-default-open.ts index 18e31d31195..8bc3898bfa3 100644 --- a/packages/session-ui/src/components/part-default-open.ts +++ b/packages/session-ui/src/components/part-default-open.ts @@ -1,4 +1,4 @@ -import type { Part as PartType, ToolPart } from "@opencode-ai/sdk/v2" +import type { Part, ToolPart } from "../presentation" function deletionOnly(part: ToolPart) { if (!("metadata" in part.state)) return false @@ -21,7 +21,7 @@ function deletionOnly(part: ToolPart) { return filediff.additions === 0 && typeof filediff.deletions === "number" && filediff.deletions > 0 } -export function partDefaultOpen(part: PartType, shell = false, edit = false): boolean | undefined { +export function partDefaultOpen(part: Part, shell = false, edit = false): boolean | undefined { if (part.type !== "tool") return undefined if (part.tool === "bash" || part.tool === "shell" || part.tool === "execute") return shell if (part.tool === "edit" || part.tool === "write" || part.tool === "patch" || part.tool === "apply_patch") { diff --git a/packages/session-ui/src/components/session-diff.ts b/packages/session-ui/src/components/session-diff.ts index 2fbd022235f..e6d9a86a26a 100644 --- a/packages/session-ui/src/components/session-diff.ts +++ b/packages/session-ui/src/components/session-diff.ts @@ -1,7 +1,7 @@ import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs" import { parsePatch } from "diff" -import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/client/promise" +import type { PresentationFileDiff } from "../presentation" type LegacyDiff = { file: string @@ -13,8 +13,7 @@ type LegacyDiff = { status?: "added" | "deleted" | "modified" } -type SnapshotDiff = SnapshotFileDiff & { file: string } -type ReviewDiff = SnapshotDiff | FileDiffInfo | VcsFileDiff | LegacyDiff +type ReviewDiff = (PresentationFileDiff & { file: string }) | FileDiffInfo | LegacyDiff export type DiffSource = Pick export type ViewDiff = { diff --git a/packages/session-ui/src/components/session-retry.tsx b/packages/session-ui/src/components/session-retry.tsx index cf2d6c94591..1ce2128d62f 100644 --- a/packages/session-ui/src/components/session-retry.tsx +++ b/packages/session-ui/src/components/session-retry.tsx @@ -1,5 +1,5 @@ import { createEffect, createMemo, createSignal, on, onCleanup, Show } from "solid-js" -import type { SessionStatus } from "@opencode-ai/sdk/v2/client" +import type { SessionStatus } from "@opencode-ai/client/promise" import { useI18n } from "@opencode-ai/ui/context/i18n" import { Card } from "@opencode-ai/ui/card" import { Tooltip } from "@opencode-ai/ui/tooltip" diff --git a/packages/session-ui/src/components/session-review.tsx b/packages/session-ui/src/components/session-review.tsx index 1585a8aa327..207f34ea89b 100644 --- a/packages/session-ui/src/components/session-review.tsx +++ b/packages/session-ui/src/components/session-review.tsx @@ -11,12 +11,12 @@ import { Tooltip } from "@opencode-ai/ui/tooltip" import { ScrollView } from "@opencode-ai/ui/scroll-view" import { useFileComponent } from "@opencode-ai/ui/context/file" import { useI18n } from "@opencode-ai/ui/context/i18n" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" -import { checksum } from "@opencode-ai/core/util/encode" +import { getDirectory, getFilename } from "@opencode-ai/util/path" +import { checksum } from "@opencode-ai/util/encode" import { createEffect, createMemo, For, Match, onCleanup, Show, Switch, untrack, type JSX } from "solid-js" import { createStore } from "solid-js/store" -import { type FileContent, type SnapshotFileDiff, type VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/client/promise" +import type { PresentationFileContent, PresentationFileDiff } from "../presentation" import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" import { type SelectedLineRange } from "@pierre/diffs" import { Dynamic } from "solid-js/web" @@ -63,10 +63,10 @@ export type SessionReviewCommentActions = { export type SessionReviewFocus = { file: string; id: string } -type RawReviewDiff = (SnapshotFileDiff | FileDiffInfo | VcsFileDiff) & { +type RawReviewDiff = (PresentationFileDiff | FileDiffInfo) & { preloaded?: PreloadMultiFileDiffResult } -type ReviewDiff = ((SnapshotFileDiff & { file: string }) | FileDiffInfo | VcsFileDiff) & { +type ReviewDiff = ((PresentationFileDiff & { file: string }) | FileDiffInfo) & { preloaded?: PreloadMultiFileDiffResult } type Item = ViewDiff & { preloaded?: PreloadMultiFileDiffResult } @@ -116,7 +116,7 @@ export interface SessionReviewProps { actions?: JSX.Element diffs: RawReviewDiff[] onViewFile?: (file: string) => void - readFile?: (path: string) => Promise + readFile?: (path: string) => Promise lineCommentMention?: LineCommentEditorProps["mention"] } diff --git a/packages/session-ui/src/components/session-turn.tsx b/packages/session-ui/src/components/session-turn.tsx index ce5f0b545ee..89ffefa9e46 100644 --- a/packages/session-ui/src/components/session-turn.tsx +++ b/packages/session-ui/src/components/session-turn.tsx @@ -1,16 +1,10 @@ -import { - AssistantMessage, - type SnapshotFileDiff, - Message as MessageType, - Part as PartType, -} from "@opencode-ai/sdk/v2/client" -import type { FileDiffInfo } from "@opencode-ai/client/promise" -import type { SessionStatus } from "@opencode-ai/sdk/v2" +import type { FileDiffInfo, SessionStatus } from "@opencode-ai/client/promise" +import type { AssistantMessage, Message as MessageType, Part, PresentationFileDiff } from "../presentation" import { useData } from "../context" import { useFileComponent } from "@opencode-ai/ui/context/file" -import { Binary } from "@opencode-ai/core/util/binary" -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { Binary } from "@opencode-ai/util/binary" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js" import { createStore } from "solid-js/store" import { Dynamic } from "solid-js/web" @@ -91,15 +85,15 @@ function list(value: T[] | undefined | null, fallback: T[]) { return fallback } -type SummaryDiff = (SnapshotFileDiff & { file: string }) | FileDiffInfo +type SummaryDiff = (PresentationFileDiff & { file: string }) | FileDiffInfo -function summaryDiff(value: SnapshotFileDiff): value is SummaryDiff { +function summaryDiff(value: PresentationFileDiff): value is SummaryDiff { return typeof value.file === "string" } const hidden = new Set(["todowrite"]) -function partState(part: PartType, showReasoningSummaries: boolean) { +function partState(part: Part, showReasoningSummaries: boolean) { if (part.type === "tool") { if (hidden.has(part.tool)) return if (part.tool === "question" && (part.state.status === "pending" || part.state.status === "running")) return @@ -174,7 +168,7 @@ export function SessionTurn( const fileComponent = useFileComponent() const emptyMessages: MessageType[] = [] - const emptyParts: PartType[] = [] + const emptyParts: Part[] = [] const emptyAssistant: AssistantMessage[] = [] const emptyDiffs: SummaryDiff[] = [] const idle = { type: "idle" as const } diff --git a/packages/session-ui/src/components/timeline-playground.stories.tsx b/packages/session-ui/src/components/timeline-playground.stories.tsx index 120a45f63d8..ab8717c9166 100644 --- a/packages/session-ui/src/components/timeline-playground.stories.tsx +++ b/packages/session-ui/src/components/timeline-playground.stories.tsx @@ -11,7 +11,7 @@ import type { ToolPart, FilePart, AgentPart, -} from "@opencode-ai/sdk/v2" +} from "../presentation" import { DataProvider } from "../context/data" import { FileComponentProvider } from "@opencode-ai/ui/context/file" import { SessionTurn } from "./session-turn" diff --git a/packages/session-ui/src/context/data.tsx b/packages/session-ui/src/context/data.tsx index 13a37fdccb2..5d12c990039 100644 --- a/packages/session-ui/src/context/data.tsx +++ b/packages/session-ui/src/context/data.tsx @@ -1,12 +1,12 @@ -import type { Message, Part, SnapshotFileDiff, SessionStatus, Provider } from "@opencode-ai/sdk/v2" -import type { FileDiffInfo, SessionInfo } from "@opencode-ai/client/promise" +import type { FileDiffInfo, SessionInfo, SessionStatus } from "@opencode-ai/client/promise" import { createSimpleContext } from "@opencode-ai/ui/context" import { PreloadMultiFileDiffResult } from "@pierre/diffs/ssr" +import type { Message, Part, PresentationFileDiff } from "../presentation" export type SessionSummary = Pick -export type NormalizedProviderListResponse = { - all: Map +type ProviderCatalog = { + all: Map }> default: { [key: string]: string } @@ -18,13 +18,13 @@ type Data = { name: string color?: string }[] - provider?: NormalizedProviderListResponse + provider?: ProviderCatalog session: SessionSummary[] session_status: { [sessionID: string]: SessionStatus } session_diff: { - [sessionID: string]: (SnapshotFileDiff | FileDiffInfo)[] + [sessionID: string]: (PresentationFileDiff | FileDiffInfo)[] } session_diff_preload?: { [sessionID: string]: PreloadMultiFileDiffResult[] diff --git a/packages/session-ui/src/pierre/media.ts b/packages/session-ui/src/pierre/media.ts index 1ee63c25ba9..2adf4cd837f 100644 --- a/packages/session-ui/src/pierre/media.ts +++ b/packages/session-ui/src/pierre/media.ts @@ -1,4 +1,4 @@ -import type { FileContent } from "@opencode-ai/sdk/v2" +import type { PresentationFileContent } from "../presentation" export type MediaKind = "image" | "audio" | "svg" @@ -9,7 +9,7 @@ type MediaValue = unknown function mediaRecord(value: unknown) { if (!value || typeof value !== "object") return - return value as Partial & { + return value as Partial & { content?: unknown encoding?: unknown mimeType?: unknown diff --git a/packages/session-ui/src/presentation.ts b/packages/session-ui/src/presentation.ts new file mode 100644 index 00000000000..be1d7beadf8 --- /dev/null +++ b/packages/session-ui/src/presentation.ts @@ -0,0 +1,151 @@ +// TODO: Delete this contract when Session UI renders current Client messages directly. +export type PresentationFileDiff = { + file?: string + patch?: string + before?: string + after?: string + additions: number + deletions: number + status?: "added" | "deleted" | "modified" +} + +export type PresentationFileContent = { + type: "text" | "binary" + content: string + encoding?: "base64" + mimeType?: string +} + +type MessageError = { + name: string + data?: { + message?: unknown + } +} + +export type UserMessage = { + id: string + sessionID: string + role: "user" + time: { created: number } + agent: string + model: { providerID: string; modelID: string } + summary?: { title?: string; diffs: PresentationFileDiff[] } +} + +export type AssistantMessage = { + id: string + sessionID: string + role: "assistant" + parentID: string + time: { created: number; completed?: number } + providerID: string + modelID: string + agent: string + error?: MessageError +} + +export type Message = UserMessage | AssistantMessage + +type PartBase = { + id: string + sessionID: string + messageID: string +} + +export type TextPart = PartBase & { + type: "text" + text: string + synthetic?: boolean + metadata?: Record +} + +export type ReasoningPart = PartBase & { + type: "reasoning" + text: string + metadata?: Record + time?: { start: number; end?: number } +} + +type FilePartSourceText = { + value: string + start: number + end: number +} + +export type FilePartSource = + | { type: "file"; text: FilePartSourceText; path: string } + | { + type: "symbol" + text: FilePartSourceText + path: string + range: { start: { line: number; character: number }; end: { line: number; character: number } } + name: string + kind: number + } + | { type: "resource"; text: FilePartSourceText; clientName: string; uri: string } + +export type FilePart = PartBase & { + type: "file" + mime: string + filename?: string + url: string + source?: FilePartSource +} + +export type AgentPart = PartBase & { + type: "agent" + name?: string + source?: { value?: string; start: number; end: number } +} + +export type ToolState = + | { status: "pending"; input: Record; raw?: string } + | { + status: "running" + input: Record + title?: string + metadata?: Record + time?: { start: number } + } + | { + status: "completed" + input: Record + output: string + title?: string + metadata?: Record + time?: { start: number; end: number; compacted?: number } + attachments?: FilePart[] + } + | { + status: "error" + input: Record + error: string + output?: string + metadata?: Record + time?: { start: number; end: number } + } + +export type ToolPart = PartBase & { + type: "tool" + callID?: string + tool: string + state: ToolState +} + +type PassivePart = PartBase & { + type: "subtask" | "step-start" | "step-finish" | "snapshot" | "patch" | "retry" | "compaction" +} + +export type Part = TextPart | ReasoningPart | FilePart | AgentPart | ToolPart | PassivePart + +export type Todo = { + content: string + status: string +} + +export type QuestionInfo = { + question: string +} + +export type QuestionAnswer = string[] diff --git a/packages/session-ui/src/v2/components/comment-card-v2.tsx b/packages/session-ui/src/v2/components/comment-card-v2.tsx index dbcdb243450..02f4d1cb589 100644 --- a/packages/session-ui/src/v2/components/comment-card-v2.tsx +++ b/packages/session-ui/src/v2/components/comment-card-v2.tsx @@ -1,6 +1,6 @@ import { createSignal, onCleanup, onMount, Show } from "solid-js" import { FileIcon } from "@opencode-ai/ui/file-icon" -import { getFilenameTruncated } from "@opencode-ai/core/util/path" +import { getFilenameTruncated } from "@opencode-ai/util/path" import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { AttachmentCardV2 } from "./attachment-card-v2" diff --git a/packages/session-ui/src/v2/components/prompt-input/types.ts b/packages/session-ui/src/v2/components/prompt-input/types.ts index c6b47a92ce1..9ac4f399b63 100644 --- a/packages/session-ui/src/v2/components/prompt-input/types.ts +++ b/packages/session-ui/src/v2/components/prompt-input/types.ts @@ -1,4 +1,4 @@ -import type { FilePartSource } from "@opencode-ai/sdk/v2/client" +import type { FilePartSource } from "../../../presentation" type PromptInputV2PartBase = { content: string diff --git a/packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx b/packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx index ba276a8f52c..9998a177bcd 100644 --- a/packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx +++ b/packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx @@ -1,4 +1,4 @@ -import { getDirectory, getFilename } from "@opencode-ai/core/util/path" +import { getDirectory, getFilename } from "@opencode-ai/util/path" import type { SelectedLineRange } from "@pierre/diffs" import { DiffChanges } from "@opencode-ai/ui/v2/diff-changes-v2" import { FileIcon } from "@opencode-ai/ui/file-icon" @@ -6,8 +6,8 @@ import { useFileComponent } from "@opencode-ai/ui/context/file" import { useI18n } from "@opencode-ai/ui/context/i18n" import { mediaKindFromPath } from "../../pierre/media" import { cloneSelectedLineRange, previewSelectedLines } from "../../pierre/selection-bridge" -import type { FileContent, SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2" import type { FileDiffInfo } from "@opencode-ai/client/promise" +import type { PresentationFileContent, PresentationFileDiff } from "../../presentation" import { createEffect, createMemo, onCleanup, Show, untrack } from "solid-js" import { createStore } from "solid-js/store" import { Dynamic } from "solid-js/web" @@ -28,14 +28,14 @@ import { LineCommentV2OverflowIcon } from "@opencode-ai/ui/v2/line-comment-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" import "./session-review-v2.css" -type ReviewDiff = (SnapshotFileDiff & { file: string }) | FileDiffInfo | VcsFileDiff +type ReviewDiff = (PresentationFileDiff & { file: string }) | FileDiffInfo export type SessionReviewFilePreviewV2Props = { file: string diff: ReviewDiff diffStyle: SessionReviewDiffStyle expandMode?: SessionReviewExpandMode - readFile?: (path: string) => Promise + readFile?: (path: string) => Promise onLineComment?: (comment: SessionReviewLineComment) => void onLineCommentUpdate?: (comment: SessionReviewCommentUpdate) => void onLineCommentDelete?: (comment: SessionReviewCommentDelete) => void diff --git a/packages/session-ui/tsconfig.json b/packages/session-ui/tsconfig.json index f34c43bbe65..b234c74822c 100644 --- a/packages/session-ui/tsconfig.json +++ b/packages/session-ui/tsconfig.json @@ -15,6 +15,5 @@ "strict": true, "types": ["vite/client", "bun"] }, - "exclude": ["**/*.stories.*", "**/*.mdx"], - "references": [{ "path": "../core" }] + "exclude": ["**/*.stories.*", "**/*.mdx"] } diff --git a/packages/util/src/binary.test.ts b/packages/util/src/binary.test.ts new file mode 100644 index 00000000000..1ad3ab2679c --- /dev/null +++ b/packages/util/src/binary.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test" +import { Binary } from "./binary.js" + +describe("binary search", () => { + test("finds sorted values and their insertion points", () => { + const values = [{ id: "a" }, { id: "c" }, { id: "e" }] + expect(Binary.search(values, "c", (item) => item.id)).toEqual({ found: true, index: 1 }) + expect(Binary.search(values, "d", (item) => item.id)).toEqual({ found: false, index: 2 }) + expect(Binary.search(values, "0", (item) => item.id)).toEqual({ found: false, index: 0 }) + }) + + test("inserts before the first matching sorted value", () => { + const values = [{ id: "a" }, { id: "c" }, { id: "c" }, { id: "e" }] + const item = { id: "c" } + + expect(Binary.insert(values, item, (value) => value.id)).toBe(values) + expect(values).toEqual([{ id: "a" }, item, { id: "c" }, { id: "c" }, { id: "e" }]) + }) +}) diff --git a/packages/util/src/binary.ts b/packages/util/src/binary.ts new file mode 100644 index 00000000000..bfcd9eb2707 --- /dev/null +++ b/packages/util/src/binary.ts @@ -0,0 +1,30 @@ +export namespace Binary { + export function search(array: T[], id: string, compare: (item: T) => string) { + let left = 0 + let right = array.length - 1 + + while (left <= right) { + const middle = Math.floor((left + right) / 2) + const value = compare(array[middle]) + if (value === id) return { found: true, index: middle } + if (value < id) left = middle + 1 + else right = middle - 1 + } + return { found: false, index: left } + } + + export function insert(array: T[], item: T, compare: (item: T) => string) { + const id = compare(item) + let left = 0 + let right = array.length + + while (left < right) { + const middle = Math.floor((left + right) / 2) + if (compare(array[middle]) < id) left = middle + 1 + else right = middle + } + + array.splice(left, 0, item) + return array + } +} diff --git a/packages/util/src/encode.test.ts b/packages/util/src/encode.test.ts new file mode 100644 index 00000000000..77e3e71716c --- /dev/null +++ b/packages/util/src/encode.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, test } from "bun:test" +import { base64Decode, base64Encode, checksum, sampledChecksum } from "./encode.js" + +describe("frontend encoding", () => { + test("uses unpadded URL-safe UTF-8 base64", () => { + expect(base64Encode("hello")).toBe("aGVsbG8") + expect(base64Encode("✓ à la mode")).toBe("4pyTIMOgIGxhIG1vZGU") + expect(base64Decode("4pyTIMOgIGxhIG1vZGU")).toBe("✓ à la mode") + expect(base64Decode("dXNlcjpwYXNz")).toBe("user:pass") + }) + + test("rejects invalid base64", () => { + expect(() => base64Decode("%%%")).toThrow() + }) + + test("keeps stable FNV checksums", () => { + expect(checksum("")).toBeUndefined() + expect(checksum("hello")).toBe("m3bicr") + expect(checksum("✓ à la mode")).toBe("jmczk0") + }) + + test("samples large values without changing the size boundary", () => { + const value = "abcdef".repeat(100) + expect(sampledChecksum(value, value.length)).toBe(checksum(value)) + expect(sampledChecksum(value, value.length - 1)).toBe("600:1isj1k5:1isj1k5:1isj1k5:1isj1k5:1isj1k5") + }) +}) diff --git a/packages/core/src/util/encode.ts b/packages/util/src/encode.ts similarity index 69% rename from packages/core/src/util/encode.ts rename to packages/util/src/encode.ts index eb436e715ef..33718bc0751 100644 --- a/packages/core/src/util/encode.ts +++ b/packages/util/src/encode.ts @@ -1,42 +1,39 @@ export function base64Encode(value: string) { const bytes = new TextEncoder().encode(value) - const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join("") + const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("") return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } export function base64Decode(value: string) { const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/")) - const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0)) - return new TextDecoder().decode(bytes) + return new TextDecoder().decode(Uint8Array.from(binary, (character) => character.charCodeAt(0))) } export function checksum(content: string): string | undefined { - if (!content) return undefined + if (!content) return let hash = 0x811c9dc5 - for (let i = 0; i < content.length; i++) { - hash ^= content.charCodeAt(i) + for (let index = 0; index < content.length; index++) { + hash ^= content.charCodeAt(index) hash = Math.imul(hash, 0x01000193) } return (hash >>> 0).toString(36) } export function sampledChecksum(content: string, limit = 500_000): string | undefined { - if (!content) return undefined + if (!content) return if (content.length <= limit) return checksum(content) const size = 4096 - const points = [ + return `${content.length}:${[ 0, Math.floor(content.length * 0.25), Math.floor(content.length * 0.5), Math.floor(content.length * 0.75), content.length - size, ] - const hashes = points .map((point) => { const start = Math.max(0, Math.min(content.length - size, point - Math.floor(size / 2))) return checksum(content.slice(start, start + size)) ?? "" }) - .join(":") - return `${content.length}:${hashes}` + .join(":")}` } diff --git a/packages/util/src/path.test.ts b/packages/util/src/path.test.ts new file mode 100644 index 00000000000..87dd4deb4f3 --- /dev/null +++ b/packages/util/src/path.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "bun:test" +import { getDirectory, getFilename, getFilenameTruncated, truncateMiddle } from "./path.js" + +describe("client paths", () => { + test("reads POSIX and Windows paths with the same rules", () => { + expect(getFilename("/repo/src/index.ts")).toBe("index.ts") + expect(getFilename("C:\\repo\\src\\index.ts\\")).toBe("index.ts") + expect(getDirectory("/repo/src/index.ts")).toBe("/repo/src/") + expect(getDirectory("C:\\repo\\src\\index.ts")).toBe("C:/repo/src/") + }) + + test("preserves root, UNC, mixed, and single-segment behavior", () => { + expect(getFilename("\\\\server\\share\\file")).toBe("file") + expect(getDirectory("\\\\server\\share\\file")).toBe("//server/share/") + expect(getDirectory("C:\\repo/src\\file")).toBe("C:/repo/src/") + expect(getDirectory("file")).toBe("/") + expect(getFilename(undefined)).toBe("") + expect(getDirectory("")).toBe("") + }) + + test("keeps filename truncation stable", () => { + expect(getFilenameTruncated("/repo/long-component-name.tsx", 16)).toBe("long-compon….tsx") + expect(truncateMiddle("abcdefghijklmnop", 9)).toBe("abcd…mnop") + }) +}) diff --git a/packages/core/src/util/path.ts b/packages/util/src/path.ts similarity index 73% rename from packages/core/src/util/path.ts rename to packages/util/src/path.ts index a3f2b550b59..59c9d6517f7 100644 --- a/packages/core/src/util/path.ts +++ b/packages/util/src/path.ts @@ -12,19 +12,19 @@ export function getDirectory(path: string | undefined) { return parts.slice(0, parts.length - 1).join("/") + "/" } -export function getFilenameTruncated(path: string | undefined, maxLength: number = 20) { +export function getFilenameTruncated(path: string | undefined, maxLength = 20) { const filename = getFilename(path) if (filename.length <= maxLength) return filename const lastDot = filename.lastIndexOf(".") - const ext = lastDot <= 0 ? "" : filename.slice(lastDot) - const available = maxLength - ext.length - 1 // -1 for ellipsis + const extension = lastDot <= 0 ? "" : filename.slice(lastDot) + const available = maxLength - extension.length - 1 if (available <= 0) return filename.slice(0, maxLength - 1) + "…" - return filename.slice(0, available) + "…" + ext + return filename.slice(0, available) + "…" + extension } -export function truncateMiddle(text: string, maxLength: number = 20) { +export function truncateMiddle(text: string, maxLength = 20) { if (text.length <= maxLength) return text - const available = maxLength - 1 // -1 for ellipsis + const available = maxLength - 1 const start = Math.ceil(available / 2) const end = Math.floor(available / 2) return text.slice(0, start) + "…" + text.slice(-end) diff --git a/packages/util/src/retry.test.ts b/packages/util/src/retry.test.ts new file mode 100644 index 00000000000..b84987269eb --- /dev/null +++ b/packages/util/src/retry.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from "bun:test" +import { retry } from "./retry.js" + +describe("client retry", () => { + test("retries transient failures up to the configured attempt count", async () => { + const failures: string[] = [] + const value = await retry( + async () => { + failures.push("attempt") + if (failures.length < 3) throw new Error("Failed to fetch") + return "ready" + }, + { delay: 0 }, + ) + + expect(value).toBe("ready") + expect(failures).toHaveLength(3) + }) + + test("does not retry other failures", async () => { + const failures: string[] = [] + await expect( + retry(async () => { + failures.push("attempt") + throw new Error("invalid response") + }), + ).rejects.toThrow("invalid response") + expect(failures).toHaveLength(1) + }) + + test("uses a caller-owned retry condition", async () => { + const failures: string[] = [] + await expect( + retry( + async () => { + failures.push("attempt") + throw new Error("retry me") + }, + { attempts: 2, delay: 0, retryIf: () => true }, + ), + ).rejects.toThrow("retry me") + expect(failures).toHaveLength(2) + }) +}) diff --git a/packages/util/src/retry.ts b/packages/util/src/retry.ts new file mode 100644 index 00000000000..a20a31a069b --- /dev/null +++ b/packages/util/src/retry.ts @@ -0,0 +1,45 @@ +export interface RetryOptions { + attempts?: number + delay?: number + factor?: number + maxDelay?: number + retryIf?: (error: unknown) => boolean +} + +const transientMessages = [ + "load failed", + "network connection was lost", + "network request failed", + "failed to fetch", + "econnreset", + "econnrefused", + "etimedout", + "socket hang up", +] + +function isTransientError(error: unknown) { + if (!error) return false + // oxlint-disable-next-line no-base-to-string -- Error input is intentionally normalized for message matching. + const message = String(error instanceof Error ? error.message : error).toLowerCase() + return transientMessages.some((item) => message.includes(item)) +} + +export async function retry(operation: () => Promise, options: RetryOptions = {}): Promise { + const attempts = options.attempts ?? 3 + const delay = options.delay ?? 500 + const factor = options.factor ?? 2 + const maxDelay = options.maxDelay ?? 10_000 + const retryIf = options.retryIf ?? isTransientError + let lastError: unknown + + for (let attempt = 0; attempt < attempts; attempt++) { + try { + return await operation() + } catch (error) { + lastError = error + if (attempt === attempts - 1 || !retryIf(error)) throw error + await new Promise((resolve) => setTimeout(resolve, Math.min(delay * Math.pow(factor, attempt), maxDelay))) + } + } + throw lastError +} diff --git a/turbo.json b/turbo.json index d3438cc0560..9b1be15013e 100644 --- a/turbo.json +++ b/turbo.json @@ -4,9 +4,6 @@ "globalPassThroughEnv": ["CI", "OPENCODE_DISABLE_SHARE"], "tasks": { "typecheck": {}, - "@opencode-ai/app#typecheck": { - "dependsOn": ["@opencode-ai/core#typecheck"] - }, "@opencode-ai/enterprise#typecheck": { "dependsOn": ["@opencode-ai/core#typecheck"] }, @@ -16,9 +13,6 @@ "@opencode-ai/server#typecheck": { "dependsOn": ["@opencode-ai/core#typecheck"] }, - "@opencode-ai/session-ui#typecheck": { - "dependsOn": ["@opencode-ai/core#typecheck"] - }, "@opencode-ai/simulation#typecheck": { "dependsOn": ["@opencode-ai/core#typecheck"] },