refactor(frontend): enforce package boundaries (#43177)

This commit is contained in:
Luke Parker 2026-08-18 15:49:23 +10:00 committed by GitHub
parent ea1ff90e42
commit 1f61eb5ca9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 579 additions and 283 deletions

View file

@ -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=="],

View file

@ -1,4 +1,4 @@
import { base64Encode } from "@opencode-ai/core/util/encode"
import { base64Encode } from "@opencode-ai/util/encode"
import type {
JsonValue,
OpenCodeEvent,

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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 {

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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:*",

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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,

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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 (

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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

View file

@ -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
}

View file

@ -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"

View file

@ -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"

View file

@ -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,

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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` },

View file

@ -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) {

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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()

View file

@ -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 (
<Show when={part()}>
{(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)
}

View file

@ -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"

View file

@ -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"

View file

@ -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<ProjectListOutput[number], "canonical"> & {
worktree: string
@ -257,9 +256,83 @@ export type Agent = {
steps?: number
}
export type Provider = NormalizedProviderListResponse["all"] extends Map<string, infer Item> ? 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<string, unknown>
headers: Record<string, string>
release_date: string
variants?: Record<string, Record<string, unknown>>
}
export type Provider = {
id: string
name: string
source: "env" | "config" | "custom" | "api"
env: string[]
key?: string
options: Record<string, unknown>
models: Record<string, Model>
}
export type ProviderListResponse = {
all: Map<string, Provider>
default: Record<string, string>
connected: string[]
}
export type ProviderAuthResponse = Record<string, unknown>

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -22,6 +22,5 @@
}
},
"include": ["src", "package.json"],
"exclude": ["dist", "ts-dist"],
"references": [{ "path": "../core" }]
"exclude": ["dist", "ts-dist"]
}

View file

@ -1,41 +0,0 @@
export namespace Binary {
export function search<T>(array: 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<T>(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
}
}

View file

@ -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<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
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
}

View file

@ -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"

View file

@ -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",

View file

@ -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<FileContent | undefined>
readFile?: (path: string) => Promise<PresentationFileContent | undefined>
onLoad?: () => void
onError?: (ctx: { kind: "image" | "audio" | "svg" }) => void
}

View file

@ -1,4 +1,4 @@
import { sampledChecksum } from "@opencode-ai/core/util/encode"
import { sampledChecksum } from "@opencode-ai/util/encode"
import {
areFilesEqual,
areOptionsEqual,

View file

@ -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"

View file

@ -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"

View file

@ -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,

View file

@ -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> = {}): FilePart {

View file

@ -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)

View file

@ -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"

View file

@ -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<typeof useI18n>) {
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<typeof useI18n>) {
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
<FileIcon node={{ path: props.path, type: "file" }} />
<div data-slot="apply-patch-file-name-container">
<Show when={props.path.includes("/")}>
<span data-slot="apply-patch-directory">{`\u202A${getDirectory(props.path)}\u202C`}</span>
<span data-slot="apply-patch-directory">{`\u202A${displayDirectory(props.path)}\u202C`}</span>
</Show>
<span data-slot="apply-patch-filename">{getFilename(props.path)}</span>
</div>
@ -1863,7 +1863,7 @@ ToolRegistry.register({
<BasicTool
{...props}
icon="bullet-list"
trigger={{ title: i18n.t("ui.tool.list"), subtitle: getDirectory(props.input.path || "/") }}
trigger={{ title: i18n.t("ui.tool.list"), subtitle: displayDirectory(props.input.path || "/") }}
>
<Show when={props.output}>
<div
@ -1891,7 +1891,7 @@ ToolRegistry.register({
icon="magnifying-glass-menu"
trigger={{
title: i18n.t("ui.tool.glob"),
subtitle: getDirectory(props.input.path || "/"),
subtitle: displayDirectory(props.input.path || "/"),
args: props.input.pattern ? ["pattern=" + props.input.pattern] : [],
}}
>
@ -1924,7 +1924,7 @@ ToolRegistry.register({
icon="magnifying-glass-menu"
trigger={{
title: i18n.t("ui.tool.grep"),
subtitle: getDirectory(props.input.path || "/"),
subtitle: displayDirectory(props.input.path || "/"),
args,
}}
>
@ -2344,7 +2344,7 @@ ToolRegistry.register({
</div>
<Show when={!pending() && props.input.filePath?.includes("/")}>
<div data-slot="message-part-path">
<span data-slot="message-part-directory">{getDirectory(props.input.filePath!)}</span>
<span data-slot="message-part-directory">{displayDirectory(props.input.filePath!)}</span>
</div>
</Show>
</div>
@ -2411,7 +2411,7 @@ ToolRegistry.register({
</div>
<Show when={!pending() && props.input.filePath?.includes("/")}>
<div data-slot="message-part-path">
<span data-slot="message-part-directory">{getDirectory(props.input.filePath!)}</span>
<span data-slot="message-part-directory">{displayDirectory(props.input.filePath!)}</span>
</div>
</Show>
</div>
@ -2520,7 +2520,7 @@ ToolRegistry.register({
<FileIcon node={{ path: file.relativePath, type: "file" }} />
<div data-slot="apply-patch-file-name-container">
<Show when={file.relativePath.includes("/")}>
<span data-slot="apply-patch-directory">{`\u202A${getDirectory(file.relativePath)}\u202C`}</span>
<span data-slot="apply-patch-directory">{`\u202A${displayDirectory(file.relativePath)}\u202C`}</span>
</Show>
<span data-slot="apply-patch-filename">{getFilename(file.relativePath)}</span>
</div>
@ -2593,7 +2593,7 @@ ToolRegistry.register({
</div>
<Show when={!pending() && single()!.relativePath.includes("/")}>
<div data-slot="message-part-path">
<span data-slot="message-part-directory">{getDirectory(single()!.relativePath)}</span>
<span data-slot="message-part-directory">{displayDirectory(single()!.relativePath)}</span>
</div>
</Show>
</div>

View file

@ -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<string, unknown>): PartType {
function tool(name: string, metadata: Record<string, unknown>): Part {
return {
id: `part_${name}`,
sessionID: "session",

View file

@ -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") {

View file

@ -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<LegacyDiff, "file" | "patch" | "before" | "after">
export type ViewDiff = {

View file

@ -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"

View file

@ -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<any>
}
type ReviewDiff = ((SnapshotFileDiff & { file: string }) | FileDiffInfo | VcsFileDiff) & {
type ReviewDiff = ((PresentationFileDiff & { file: string }) | FileDiffInfo) & {
preloaded?: PreloadMultiFileDiffResult<any>
}
type Item = ViewDiff & { preloaded?: PreloadMultiFileDiffResult<any> }
@ -116,7 +116,7 @@ export interface SessionReviewProps {
actions?: JSX.Element
diffs: RawReviewDiff[]
onViewFile?: (file: string) => void
readFile?: (path: string) => Promise<FileContent | undefined>
readFile?: (path: string) => Promise<PresentationFileContent | undefined>
lineCommentMention?: LineCommentEditorProps["mention"]
}

View file

@ -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<T>(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 }

View file

@ -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"

View file

@ -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<SessionInfo, "id" | "parentID" | "title" | "time">
export type NormalizedProviderListResponse = {
all: Map<string, Provider>
type ProviderCatalog = {
all: Map<string, { models: Record<string, { name: string }> }>
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<any>[]

View file

@ -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<FileContent> & {
return value as Partial<PresentationFileContent> & {
content?: unknown
encoding?: unknown
mimeType?: unknown

View file

@ -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<string, unknown>
}
export type ReasoningPart = PartBase & {
type: "reasoning"
text: string
metadata?: Record<string, unknown>
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<string, unknown>; raw?: string }
| {
status: "running"
input: Record<string, unknown>
title?: string
metadata?: Record<string, unknown>
time?: { start: number }
}
| {
status: "completed"
input: Record<string, unknown>
output: string
title?: string
metadata?: Record<string, unknown>
time?: { start: number; end: number; compacted?: number }
attachments?: FilePart[]
}
| {
status: "error"
input: Record<string, unknown>
error: string
output?: string
metadata?: Record<string, unknown>
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[]

View file

@ -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"

View file

@ -1,4 +1,4 @@
import type { FilePartSource } from "@opencode-ai/sdk/v2/client"
import type { FilePartSource } from "../../../presentation"
type PromptInputV2PartBase = {
content: string

Some files were not shown because too many files have changed in this diff Show more