mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-05 16:41:01 -04:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba47ec90df | |||
| f69ff61600 | |||
| a81628ebfe | |||
| 232ee3b10d | |||
| dcd1d457b0 | |||
| 76b318e990 | |||
| c0ab35c3c2 |
@@ -263,7 +263,7 @@ function turn(index: number, target: boolean, status: "running" | "completed" =
|
||||
),
|
||||
contextTool(contextIDs[3]!, assistantID, "list", { path: "src" }, status),
|
||||
{
|
||||
id: "prt_0104_text",
|
||||
id: followingTextID,
|
||||
sessionID,
|
||||
messageID: assistantID,
|
||||
type: "text",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
|
||||
import { Icon } from "@opencode-ai/ui/v2/icon"
|
||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||
import type { ReferenceInfo } from "@/types"
|
||||
import type { ReferenceInfo } from "@opencode-ai/client/promise"
|
||||
import { createEffect, createMemo, on, Show } from "solid-js"
|
||||
import { ModelSelectorPopoverV2 } from "@/components/dialog-select-model"
|
||||
import { DialogSelectModelUnpaidV2 } from "@/components/dialog-select-model-unpaid-v2"
|
||||
|
||||
@@ -81,7 +81,7 @@ import { promptDesignPlaceholder, promptPlaceholder } from "./prompt-input/place
|
||||
import { createPromptInputTransientState } from "./prompt-input/transient-state"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { ImagePreview } from "@opencode-ai/ui/image-preview"
|
||||
import type { ReferenceInfo } from "@/types"
|
||||
import type { ReferenceInfo } from "@opencode-ai/client/promise"
|
||||
|
||||
export { createPromptInputHistory }
|
||||
export type { PromptInputControls, PromptInputHistory, PromptInputProps, PromptInputState, PromptInputSubmission }
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import type { AgentPartInput, FilePartInput, Part, TextPartInput } from "@/types"
|
||||
import type { AgentPart as MessageAgentPart, FilePart, Part, TextPart } from "@/types"
|
||||
import type { FileSelection } from "@/context/file"
|
||||
import { encodeFilePath } from "@/context/file/path"
|
||||
import type { AgentPart, FileAttachmentPart, ImageAttachmentPart, Prompt } from "@/context/prompt"
|
||||
import { Identifier } from "@/utils/id"
|
||||
import { createCommentMetadata, formatCommentNote } from "@/utils/comment-note"
|
||||
|
||||
type PromptRequestPart = (TextPartInput | FilePartInput | AgentPartInput) & { id: string }
|
||||
type PromptRequestPart =
|
||||
| (Omit<TextPart, "id" | "sessionID" | "messageID"> & { id: string })
|
||||
| (Omit<FilePart, "id" | "sessionID" | "messageID"> & { id: string })
|
||||
| (Omit<MessageAgentPart, "id" | "sessionID" | "messageID"> & { id: string })
|
||||
|
||||
type ContextFile = {
|
||||
key: string
|
||||
|
||||
@@ -31,6 +31,12 @@ const promotedDrafts: Array<{ draftID: string; server: string; sessionId: string
|
||||
const sentPrompts: string[] = []
|
||||
const promptInputs: unknown[] = []
|
||||
const sentCommands: unknown[] = []
|
||||
const switchedAgents: Array<{ sessionID: string; agent: string }> = []
|
||||
const switchedModels: Array<{
|
||||
sessionID: string
|
||||
model: { id: string; providerID: string; variant?: string }
|
||||
}> = []
|
||||
const sessionRequestOrder: string[] = []
|
||||
const commands: Array<{ name: string }> = []
|
||||
let serverSessionSyncs = 0
|
||||
|
||||
@@ -93,10 +99,22 @@ const clientFor = (directory: string) => {
|
||||
}
|
||||
},
|
||||
prompt: async (input: unknown) => {
|
||||
sessionRequestOrder.push("prompt")
|
||||
sentPrompts.push(directory)
|
||||
promptInputs.push(input)
|
||||
return { data: undefined }
|
||||
},
|
||||
switchAgent: async (input: { sessionID: string; agent: string }) => {
|
||||
sessionRequestOrder.push("agent")
|
||||
switchedAgents.push(input)
|
||||
},
|
||||
switchModel: async (input: {
|
||||
sessionID: string
|
||||
model: { id: string; providerID: string; variant?: string }
|
||||
}) => {
|
||||
sessionRequestOrder.push("model")
|
||||
switchedModels.push(input)
|
||||
},
|
||||
command: async (input: unknown) => {
|
||||
sentCommands.push(input)
|
||||
},
|
||||
@@ -279,6 +297,9 @@ beforeEach(() => {
|
||||
sentPrompts.length = 0
|
||||
promptInputs.length = 0
|
||||
sentCommands.length = 0
|
||||
switchedAgents.length = 0
|
||||
switchedModels.length = 0
|
||||
sessionRequestOrder.length = 0
|
||||
commands.length = 0
|
||||
promptValue = [{ type: "text", content: "ls", start: 0, end: 2 }]
|
||||
params = {}
|
||||
@@ -436,13 +457,17 @@ describe("prompt submit worktree selection", () => {
|
||||
expect(promotedDrafts).toEqual([{ draftID: "draft-1", server: "project-server", sessionId: "session-1" }])
|
||||
})
|
||||
|
||||
test("includes the selected variant on optimistic prompts", async () => {
|
||||
test("switches the selected agent and model before prompting", async () => {
|
||||
params = { id: "session-1" }
|
||||
variant = "high"
|
||||
|
||||
const submit = createPromptSubmit({
|
||||
prompt,
|
||||
info: () => ({ id: "session-1" }),
|
||||
info: () => ({
|
||||
id: "session-1",
|
||||
agent: "old-agent",
|
||||
model: { id: "old-model", providerID: "old-provider" },
|
||||
}),
|
||||
imageAttachments: () => [],
|
||||
commentCount: () => 0,
|
||||
autoAccept: () => false,
|
||||
@@ -471,6 +496,14 @@ describe("prompt submit worktree selection", () => {
|
||||
},
|
||||
})
|
||||
expect(sentPrompts).toEqual(["/repo/main"])
|
||||
expect(switchedAgents).toEqual([{ sessionID: "session-1", agent: "agent" }])
|
||||
expect(switchedModels).toEqual([
|
||||
{
|
||||
sessionID: "session-1",
|
||||
model: { id: "model", providerID: "provider", variant: "high" },
|
||||
},
|
||||
])
|
||||
expect(sessionRequestOrder).toEqual(["agent", "model", "prompt"])
|
||||
expect(promptInputs[0]).toMatchObject({
|
||||
sessionID: "session-1",
|
||||
text: "ls",
|
||||
|
||||
@@ -45,6 +45,7 @@ type FollowupSendInput = {
|
||||
api: DirectorySDK["api"]["session"]
|
||||
serverSync: ServerSync
|
||||
sync: DirectorySync
|
||||
session: Accessor<{ agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined>
|
||||
draft: FollowupDraft
|
||||
messageID?: string
|
||||
optimisticBusy?: boolean
|
||||
@@ -157,6 +158,25 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||
return false
|
||||
}
|
||||
|
||||
const session = input.session()
|
||||
if (session?.agent !== input.draft.agent) {
|
||||
await input.api.switchAgent({ sessionID: input.draft.sessionID, agent: input.draft.agent })
|
||||
}
|
||||
if (
|
||||
session?.model?.providerID !== input.draft.model.providerID ||
|
||||
session.model.id !== input.draft.model.modelID ||
|
||||
(session.model.variant ?? "default") !== (input.draft.variant ?? "default")
|
||||
) {
|
||||
await input.api.switchModel({
|
||||
sessionID: input.draft.sessionID,
|
||||
model: {
|
||||
id: input.draft.model.modelID,
|
||||
providerID: input.draft.model.providerID,
|
||||
variant: input.draft.variant,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
await input.api.prompt({
|
||||
sessionID: input.draft.sessionID,
|
||||
id: messageID,
|
||||
@@ -197,7 +217,9 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
|
||||
|
||||
type PromptSubmitInput = {
|
||||
prompt: ReturnType<typeof usePrompt>
|
||||
info: Accessor<{ id: string } | undefined>
|
||||
info: Accessor<
|
||||
{ id: string; agent?: string; model?: { id: string; providerID: string; variant?: string } } | undefined
|
||||
>
|
||||
imageAttachments: Accessor<ImageAttachmentPart[]>
|
||||
commentCount: Accessor<number>
|
||||
autoAccept: Accessor<boolean>
|
||||
@@ -595,6 +617,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||
api: sdk().api.session,
|
||||
sync: sync(),
|
||||
serverSync: serverSync(),
|
||||
session: () => input.info() ?? session,
|
||||
draft,
|
||||
messageID,
|
||||
optimisticBusy: sessionDirectory === projectDirectory,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type {
|
||||
Config,
|
||||
Path,
|
||||
PermissionRequest,
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
Session,
|
||||
} from "@/types"
|
||||
import type {
|
||||
@@ -17,11 +14,14 @@ import type {
|
||||
CommandListOutput,
|
||||
LocationGetInput,
|
||||
LocationGetOutput,
|
||||
PermissionRequest,
|
||||
ProjectCurrentInput,
|
||||
ProjectCurrentOutput,
|
||||
ProjectListOutput,
|
||||
ReferenceListInput,
|
||||
ReferenceListOutput,
|
||||
ReferenceInfo,
|
||||
QuestionRequest,
|
||||
SessionApi,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { showToast } from "@/utils/toast"
|
||||
@@ -34,7 +34,6 @@ import type { ServerSession } from "../server-session"
|
||||
import {
|
||||
cmp,
|
||||
normalizeAgentList,
|
||||
normalizePermissionRequest,
|
||||
normalizeProjectInfo,
|
||||
normalizeProviderList,
|
||||
} from "./utils"
|
||||
@@ -368,7 +367,7 @@ export async function bootstrapDirectory(input: {
|
||||
retry(() =>
|
||||
input.api.permission.request
|
||||
.list({ location: { directory: input.directory } })
|
||||
.then((result) => result.data.map(normalizePermissionRequest))
|
||||
.then((result) => result.data)
|
||||
.then((permissions) => {
|
||||
const ids = permissions.map((permission) => permission.sessionID)
|
||||
const grouped = groupBySession(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { Message, Part, PermissionRequest, Project, QuestionRequest, Session } from "@/types"
|
||||
import type { Message, Part, Project, Session } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest } from "@opencode-ai/client/promise"
|
||||
import { createStore } from "solid-js/store"
|
||||
import type { State } from "./types"
|
||||
import { applyDirectoryEvent, applyGlobalEvent, cleanupDroppedSessionCaches } from "./event-reducer"
|
||||
@@ -38,10 +39,10 @@ const permissionRequest = (id: string, sessionID: string, title = id) =>
|
||||
({
|
||||
id,
|
||||
sessionID,
|
||||
permission: title,
|
||||
patterns: ["*"],
|
||||
action: title,
|
||||
resources: ["*"],
|
||||
metadata: {},
|
||||
always: [],
|
||||
save: [],
|
||||
}) as PermissionRequest
|
||||
|
||||
const questionRequest = (id: string, sessionID: string, title = id) =>
|
||||
@@ -512,7 +513,7 @@ describe("applyDirectoryEvent", () => {
|
||||
directory: "/tmp",
|
||||
loadLsp() {},
|
||||
})
|
||||
expect(store.permission[sessionID]?.find((x) => x.id === "perm_2")?.permission).toBe("updated")
|
||||
expect(store.permission[sessionID]?.find((x) => x.id === "perm_2")?.action).toBe("updated")
|
||||
|
||||
applyDirectoryEvent({
|
||||
event: { type: "permission.replied", properties: { sessionID, requestID: "perm_2" } },
|
||||
|
||||
@@ -3,14 +3,11 @@ import { produce, reconcile, type SetStoreFunction, type Store } from "solid-js/
|
||||
import type {
|
||||
Message,
|
||||
Part,
|
||||
PermissionRequest,
|
||||
Project,
|
||||
QuestionRequest,
|
||||
Session,
|
||||
SessionStatus,
|
||||
Todo,
|
||||
} from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { State, VcsCache } from "./types"
|
||||
import { trimSessions } from "./session-trim"
|
||||
import { dropSessionCaches } from "./session-cache"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { SessionV2Info } from "@/types"
|
||||
import type { SessionInfo } from "@opencode-ai/client/promise"
|
||||
import {
|
||||
applyHomeSessionEvent,
|
||||
appendHomeSessionEvent,
|
||||
@@ -48,9 +48,7 @@ describe("Home V2 session index", () => {
|
||||
calls.push({ input, signal: options.signal })
|
||||
if (!("cursor" in input)) {
|
||||
return {
|
||||
data: Array.from({ length: HOME_V2_SESSION_PAGE_LIMIT }, (_, index) =>
|
||||
session({ id: `page-1-${index}` }),
|
||||
),
|
||||
data: Array.from({ length: HOME_V2_SESSION_PAGE_LIMIT }, (_, index) => session({ id: `page-1-${index}` })),
|
||||
cursor: { next: "next-page" },
|
||||
}
|
||||
}
|
||||
@@ -74,7 +72,7 @@ describe("Home V2 session index", () => {
|
||||
const activeNull = {
|
||||
...session({ id: "active-null", updated: 20 }),
|
||||
time: { created: 1, updated: 20, archived: null },
|
||||
} as unknown as SessionV2Info
|
||||
} as unknown as SessionInfo
|
||||
const result = parseHomeSessionIndex([
|
||||
session({ id: "root", updated: 30 }),
|
||||
activeNull,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Event, Session, SessionV2Info, V2SessionListResponse } from "@/types"
|
||||
import type { Event, Session } from "@/types"
|
||||
import type { SessionInfo, SessionsResponse } from "@opencode-ai/client/promise"
|
||||
import { withTimestampedFallback } from "@opencode-ai/util/session-title-fallback"
|
||||
import type { QueryClient } from "@tanstack/solid-query"
|
||||
import { trimSessions } from "./session-trim"
|
||||
@@ -26,11 +27,11 @@ export async function loadHomeSessionIndex(
|
||||
list: (
|
||||
input: { limit: number; order: "desc"; cursor?: string },
|
||||
options: { signal?: AbortSignal },
|
||||
) => Promise<V2SessionListResponse>,
|
||||
) => Promise<SessionsResponse>,
|
||||
eventSequence = 0,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
const data: SessionV2Info[] = []
|
||||
const data: SessionInfo[] = []
|
||||
let cursor: string | undefined
|
||||
|
||||
for (;;) {
|
||||
@@ -126,7 +127,7 @@ export function createHomeSessionIndexCache(queryClient: QueryClient, server: st
|
||||
// multiple directories. A bounded page could omit an old session updated today.
|
||||
// Once released, use client.v2.project.list() and client.v2.session.list({
|
||||
// parentID: null, order: "desc" }), then remove this adapter and its V1 fields.
|
||||
export function parseHomeSessionIndex(sessions: SessionV2Info[]): Session[] {
|
||||
export function parseHomeSessionIndex(sessions: SessionInfo[]): Session[] {
|
||||
return sessions.flatMap((item) => {
|
||||
if (item.parentID || typeof item.time.archived === "number") return []
|
||||
return [toLegacySummary(item)]
|
||||
@@ -141,7 +142,7 @@ export function retainHomeSessions(sessions: Session[], limit: number, now: numb
|
||||
export function applyHomeSessionEvent(sessions: Session[], event: HomeSessionEvent) {
|
||||
const info = event.properties.info
|
||||
const index = sessions.findIndex((session) => session.id === info.id)
|
||||
if (event.type === "session.deleted" || info.parentID || typeof info.time.archived === "number") {
|
||||
if (event.type === "session.deleted" || info.parentID || typeof info.time.archived === "number") {
|
||||
if (index === -1) return sessions
|
||||
return sessions.toSpliced(index, 1)
|
||||
}
|
||||
@@ -150,7 +151,7 @@ export function applyHomeSessionEvent(sessions: Session[], event: HomeSessionEve
|
||||
return sessions.with(index, info)
|
||||
}
|
||||
|
||||
function toLegacySummary(session: SessionV2Info): Session {
|
||||
function toLegacySummary(session: SessionInfo): Session {
|
||||
return {
|
||||
id: session.id,
|
||||
slug: session.id,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { Message, Part, PermissionRequest, QuestionRequest, SessionStatus, Todo } from "@/types"
|
||||
import type { Message, Part, Todo } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import { dropSessionCaches, pickSessionCacheEvictions } from "./session-cache"
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Message, Part, PermissionRequest, QuestionRequest, SessionStatus, Todo } from "@/types"
|
||||
import type { Message, Part, Todo } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { PermissionRequest, Session } from "@/types"
|
||||
import type { Session } from "@/types"
|
||||
import type { PermissionRequest } from "@opencode-ai/client/promise"
|
||||
import { trimSessions } from "./session-trim"
|
||||
|
||||
const session = (input: { id: string; parentID?: string; created: number; updated?: number; archived?: number }) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PermissionRequest, Session } from "@/types"
|
||||
import type { Session } from "@/types"
|
||||
import type { PermissionRequest } from "@opencode-ai/client/promise"
|
||||
import { cmp } from "./utils"
|
||||
import { SESSION_RECENT_LIMIT, SESSION_RECENT_WINDOW } from "./types"
|
||||
|
||||
|
||||
@@ -5,15 +5,17 @@ import type {
|
||||
Message,
|
||||
Part,
|
||||
Path,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
Session,
|
||||
SessionStatus,
|
||||
Todo,
|
||||
VcsInfo,
|
||||
} from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type {
|
||||
FileDiffInfo,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
SessionStatus,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import type { CommandInfo, McpResource, McpServer, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type { Accessor } from "solid-js"
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
ModelListOutput,
|
||||
ProviderListOutput,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { directoryKey, normalizeAgentList, normalizePermissionRequest, normalizeProviderList } from "./utils"
|
||||
import { directoryKey, normalizeAgentList, normalizeProviderList } from "./utils"
|
||||
|
||||
describe("normalizeAgentList", () => {
|
||||
test("adapts current agents to the app agent shape", () => {
|
||||
@@ -43,30 +43,6 @@ describe("normalizeAgentList", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("normalizePermissionRequest", () => {
|
||||
test("adapts the current permission request to app state", () => {
|
||||
expect(
|
||||
normalizePermissionRequest({
|
||||
id: "permission-1",
|
||||
sessionID: "session-1",
|
||||
action: "read",
|
||||
resources: ["README.md"],
|
||||
save: ["*.md"],
|
||||
metadata: { path: "README.md" },
|
||||
source: { type: "tool", messageID: "message-1", id: "call-1" },
|
||||
}),
|
||||
).toEqual({
|
||||
id: "permission-1",
|
||||
sessionID: "session-1",
|
||||
permission: "read",
|
||||
patterns: ["README.md"],
|
||||
always: ["*.md"],
|
||||
metadata: { path: "README.md" },
|
||||
tool: { messageID: "message-1", callID: "call-1" },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("normalizeProviderList", () => {
|
||||
test("groups current models into the app provider catalog", () => {
|
||||
const result = normalizeProviderList(
|
||||
|
||||
@@ -2,10 +2,9 @@ import type {
|
||||
AgentListOutput,
|
||||
ModelDefaultOutput,
|
||||
ModelListOutput,
|
||||
PermissionRequest,
|
||||
ProviderListOutput,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import type { Agent, Event, Project, Provider, ProviderListResponse } from "@/types"
|
||||
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"
|
||||
@@ -36,22 +35,6 @@ export function normalizeAgentList(input: AgentListOutput["data"] | Agent[]): Ag
|
||||
}))
|
||||
}
|
||||
|
||||
type LegacyPermissionRequest = Extract<Event, { type: "permission.asked" }>["properties"]
|
||||
|
||||
export function normalizePermissionRequest(input: PermissionRequest | LegacyPermissionRequest): LegacyPermissionRequest {
|
||||
if ("permission" in input) return input
|
||||
return {
|
||||
id: input.id,
|
||||
sessionID: input.sessionID,
|
||||
permission: input.action,
|
||||
patterns: input.resources,
|
||||
always: input.save ?? [],
|
||||
metadata: input.metadata ?? {},
|
||||
tool:
|
||||
input.source?.type === "tool" ? { messageID: input.source.messageID, callID: input.source.id } : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProviderList(
|
||||
providers: ProviderListOutput["data"] | ProviderListResponse,
|
||||
models?: ModelListOutput["data"],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { PermissionRequest, Session } from "@/types"
|
||||
import type { Session } from "@/types"
|
||||
import type { PermissionRequest } from "@opencode-ai/client/promise"
|
||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||
import { autoRespondsPermission, isDirectoryAutoAccepting, sessionAutoAccept } from "./permission-auto-respond"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import type { PermissionRequest } from "@/types"
|
||||
import type { PermissionRequest } from "@opencode-ai/client/promise"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { ServerSDK } from "@/context/server-sdk"
|
||||
import type { ServerSync } from "./server-sync"
|
||||
@@ -13,7 +13,6 @@ import { type DraftTab, useTabs } from "./tabs"
|
||||
import { useSettings } from "./settings"
|
||||
import { requireServerKey } from "@/utils/session-route"
|
||||
import type { ServerScope } from "@/utils/server-scope"
|
||||
import { normalizePermissionRequest } from "./global-sync/utils"
|
||||
import {
|
||||
acceptKey,
|
||||
directoryAcceptKey,
|
||||
@@ -256,9 +255,7 @@ function createServerPermissionState(input: { sdk: ServerSDK; sync: ServerSync }
|
||||
}
|
||||
|
||||
const list = async (directory: string) => {
|
||||
return input.sdk.api.permission.request
|
||||
.list({ location: { directory } })
|
||||
.then((result) => result.data.map(normalizePermissionRequest))
|
||||
return input.sdk.api.permission.request.list({ location: { directory } }).then((result) => result.data)
|
||||
}
|
||||
|
||||
function respondOnce(permission: PermissionRequest, directory?: string) {
|
||||
|
||||
@@ -15,7 +15,7 @@ describe("resumeStreamAfterPageShow", () => {
|
||||
})
|
||||
|
||||
describe("adaptServerEvent", () => {
|
||||
test("preserves current events while adapting permission requests for existing consumers", () => {
|
||||
test("preserves current permission requests", () => {
|
||||
const current = {
|
||||
id: "evt_1",
|
||||
created: 1,
|
||||
@@ -35,9 +35,9 @@ describe("adaptServerEvent", () => {
|
||||
properties: {
|
||||
id: "perm_1",
|
||||
sessionID: "ses_1",
|
||||
permission: "read",
|
||||
patterns: ["src/**"],
|
||||
tool: { messageID: "msg_1", callID: "call_1" },
|
||||
action: "read",
|
||||
resources: ["src/**"],
|
||||
source: { type: "tool", messageID: "msg_1", id: "call_1" },
|
||||
},
|
||||
current,
|
||||
})
|
||||
@@ -87,11 +87,7 @@ describe("current event buffering", () => {
|
||||
test("preserves boundaries between distinct delta streams", () => {
|
||||
const events = [delta("evt_1", "a"), delta("evt_2", "b", 1), delta("evt_3", "c")]
|
||||
|
||||
expect(coalesceServerEvents(events).map((event) => event.payload.current?.id)).toEqual([
|
||||
"evt_1",
|
||||
"evt_2",
|
||||
"evt_3",
|
||||
])
|
||||
expect(coalesceServerEvents(events).map((event) => event.payload.current?.id)).toEqual(["evt_1", "evt_2", "evt_3"])
|
||||
})
|
||||
|
||||
test("preserves current event order when enqueuing", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { OpenCodeEvent } from "@opencode-ai/client/promise"
|
||||
import type { Event, PermissionRequest } from "@/types"
|
||||
import type { Event } from "@/types"
|
||||
import { createSimpleContext } from "@opencode-ai/ui/context"
|
||||
import { createGlobalEmitter } from "@solid-primitives/event-bus"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
@@ -24,25 +24,6 @@ type CurrentDelta = Extract<
|
||||
>
|
||||
|
||||
export function adaptServerEvent(event: OpenCodeEvent): ServerEvent {
|
||||
if (event.type === "permission.asked") {
|
||||
return {
|
||||
id: event.id,
|
||||
type: "permission.asked",
|
||||
properties: {
|
||||
id: event.data.id,
|
||||
sessionID: event.data.sessionID,
|
||||
permission: event.data.action,
|
||||
patterns: event.data.resources,
|
||||
always: event.data.save ?? [],
|
||||
metadata: event.data.metadata ?? {},
|
||||
tool:
|
||||
event.data.source?.type === "tool"
|
||||
? { messageID: event.data.source.messageID, callID: event.data.source.id }
|
||||
: undefined,
|
||||
} satisfies PermissionRequest,
|
||||
current: event,
|
||||
}
|
||||
}
|
||||
return { id: event.id, type: event.type, properties: event.data, current: event } as ServerEvent
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@ import type { OpenCodeEvent, SessionApi, SessionMessageInfo } from "@opencode-ai
|
||||
import type {
|
||||
Message,
|
||||
Part,
|
||||
PermissionRequest,
|
||||
QuestionRequest,
|
||||
Session,
|
||||
SessionStatus,
|
||||
Todo,
|
||||
} from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, PermissionRequest, QuestionRequest, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import { batch } from "solid-js"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { rootSession } from "@/utils/session-route"
|
||||
|
||||
@@ -3,7 +3,6 @@ import type {
|
||||
Path,
|
||||
Project,
|
||||
ProviderAuthResponse,
|
||||
SessionStatus,
|
||||
} from "@/types"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
@@ -55,6 +54,7 @@ import type {
|
||||
McpResourceCatalogOutput,
|
||||
McpServer,
|
||||
SessionActiveOutput,
|
||||
SessionStatus,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { toggleMcp } from "./global-sync/mcp"
|
||||
import { createServerSession, type ServerSession } from "./server-session"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FilePart, Project, UserMessage, VcsFileDiff } from "@/types"
|
||||
import type { FilePart, Project, UserMessage } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
import { createQuery, skipToken, useMutation, useQueryClient } from "@tanstack/solid-query"
|
||||
@@ -718,13 +719,13 @@ export default function Page() {
|
||||
if (reviewMode() === "git" || reviewMode() === "branch") return !vcsQuery.isPending
|
||||
return true
|
||||
}
|
||||
const loadReviewDiff = async (file: string, version?: number): Promise<VcsFileDiff | undefined> => {
|
||||
const loadReviewDiff = async (file: string, version?: number): Promise<FileDiffInfo | undefined> => {
|
||||
const mode = vcsMode()
|
||||
if (!mode) return
|
||||
const root = reviewRootDirectory(sync().project?.worktree ?? sdk().directory)
|
||||
const directory = reviewDiffDirectory(root, file)
|
||||
const source = reviewDiffs().find((diff) => diff.file === file)
|
||||
const valid = (diff: VcsFileDiff | undefined) => {
|
||||
const valid = (diff: FileDiffInfo | undefined) => {
|
||||
if (!diff || !source) return
|
||||
if (diff.additions !== source.additions || diff.deletions !== source.deletions) return
|
||||
if (reviewDiffNeedsLoad(diff)) return
|
||||
@@ -1721,6 +1722,7 @@ export default function Page() {
|
||||
api: sdk().api.session,
|
||||
sync: sync(),
|
||||
serverSync: serverSync(),
|
||||
session: () => sync().session.get(input.sessionID),
|
||||
draft: item,
|
||||
optimisticBusy: item.sessionDirectory === sdk().directory,
|
||||
}).catch((err) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { PermissionRequest, QuestionRequest, Session } from "@/types"
|
||||
import type { Session } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest } from "@opencode-ai/client/promise"
|
||||
import { todoDockAtBoundary, todoState } from "./session-composer-state"
|
||||
import { sessionPermissionRequest, sessionQuestionRequest } from "./session-request-tree"
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createEffect, createMemo, on, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import type { PermissionRequest, QuestionRequest, Todo } from "@/types"
|
||||
import type { Todo } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest } from "@opencode-ai/client/promise"
|
||||
import { useParams } from "@solidjs/router"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { useServerSync } from "@/context/server-sync"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { For, Show } from "solid-js"
|
||||
import type { PermissionRequest } from "@/types"
|
||||
import type { PermissionRequest } from "@opencode-ai/client/promise"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
@@ -13,7 +13,7 @@ export function SessionPermissionDock(props: {
|
||||
const language = useLanguage()
|
||||
|
||||
const toolDescription = () => {
|
||||
const key = `settings.permissions.tool.${props.request.permission}.description`
|
||||
const key = `settings.permissions.tool.${props.request.action}.description`
|
||||
const value = language.t(key as Parameters<typeof language.t>[0])
|
||||
if (value === key) return ""
|
||||
return value
|
||||
@@ -59,11 +59,11 @@ export function SessionPermissionDock(props: {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.request.patterns.length > 0}>
|
||||
<Show when={props.request.resources.length > 0}>
|
||||
<div data-slot="permission-row">
|
||||
<span data-slot="permission-spacer" aria-hidden="true" />
|
||||
<div data-slot="permission-patterns">
|
||||
<For each={props.request.patterns}>
|
||||
<For each={props.request.resources}>
|
||||
{(pattern) => <code class="text-12-regular text-text-base break-all">{pattern}</code>}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DockPrompt } from "@opencode-ai/session-ui/dock-prompt"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { useSpring } from "@opencode-ai/ui/motion-spring"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import type { QuestionAnswer, QuestionRequest } from "@/types"
|
||||
import type { QuestionAnswer, QuestionRequest } from "@opencode-ai/client/promise"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PermissionRequest, QuestionRequest, Session } from "@/types"
|
||||
import type { Session } from "@/types"
|
||||
import type { PermissionRequest, QuestionRequest } from "@opencode-ai/client/promise"
|
||||
|
||||
function sessionTreeRequest<T>(
|
||||
session: Session[],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createEffect, onCleanup, type JSX } from "solid-js"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import { SessionReview } from "@opencode-ai/session-ui/session-review"
|
||||
import type {
|
||||
SessionReviewCommentActions,
|
||||
@@ -15,7 +14,7 @@ import type { LineComment } from "@/context/comments"
|
||||
|
||||
export type DiffStyle = "unified" | "split"
|
||||
|
||||
type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
||||
type ReviewDiff = FileDiffInfo | FileDiffLegacyInfo
|
||||
|
||||
export interface SessionReviewTabProps {
|
||||
title?: JSX.Element
|
||||
|
||||
@@ -23,8 +23,7 @@ import { Mark } from "@opencode-ai/ui/logo"
|
||||
import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
|
||||
import { KeybindV2 } from "@opencode-ai/ui/v2/keybind-v2"
|
||||
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import { ConstrainDragYAxis, getDraggableId } from "@/utils/solid-dnd"
|
||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||
|
||||
@@ -57,8 +56,8 @@ import { setSessionHandoff } from "@/pages/session/handoff"
|
||||
import { useSessionLayout } from "@/pages/session/session-layout"
|
||||
import { SessionFileBrowserTab, type SessionFileBrowserState } from "@/pages/session/v2/session-file-browser-tab"
|
||||
|
||||
type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
||||
type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff
|
||||
type ReviewDiff = FileDiffInfo | FileDiffLegacyInfo
|
||||
type RenderDiff = FileDiffInfo | (FileDiffLegacyInfo & { file: string })
|
||||
|
||||
function renderDiff(value: ReviewDiff): value is RenderDiff {
|
||||
return typeof value.file === "string"
|
||||
|
||||
@@ -45,8 +45,7 @@ test("reports a divergent native offset once and ignores equal offsets and unrel
|
||||
|
||||
route.remove()
|
||||
document.body.append(route)
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
await frames(3)
|
||||
await waitFor(() => calls.length === 1)
|
||||
expect(calls).toEqual([[0, false]])
|
||||
|
||||
route.remove()
|
||||
@@ -197,3 +196,8 @@ async function frames(count: number) {
|
||||
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
||||
}
|
||||
}
|
||||
|
||||
async function waitFor(condition: () => boolean) {
|
||||
const deadline = performance.now() + 1_000
|
||||
while (!condition() && performance.now() < deadline) await frames(1)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type { AssistantMessage, Message, Part, SessionStatus, UserMessage } from "@/types"
|
||||
import type { SessionMessageInfo, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { AssistantMessage, Message, Part, UserMessage } from "@/types"
|
||||
import { createMemo, type Accessor } from "solid-js"
|
||||
import { reuseTimelineRows } from "./row-reconciliation"
|
||||
import { Timeline, TimelineRow } from "./rows"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note"
|
||||
import type { SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type { AssistantMessage, Part, SessionStatus, UserMessage } from "@/types"
|
||||
import type { SessionMessageInfo, SessionStatus } from "@opencode-ai/client/promise"
|
||||
import type { AssistantMessage, Part, UserMessage } from "@/types"
|
||||
import { groupParts, renderable, type PartGroup } from "@opencode-ai/session-ui/message-part"
|
||||
import { TimelineRow, type SummaryDiff } from "./timeline-row"
|
||||
import { uniqueSummaryDiffs } from "./summary-diffs"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { SnapshotFileDiff } from "@/types"
|
||||
import type { FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import { uniqueSummaryDiffs } from "./summary-diffs"
|
||||
|
||||
const diff = (file: string, additions: number) =>
|
||||
@@ -7,13 +7,13 @@ const diff = (file: string, additions: number) =>
|
||||
file,
|
||||
additions,
|
||||
deletions: 0,
|
||||
}) satisfies SnapshotFileDiff
|
||||
}) satisfies FileDiffLegacyInfo
|
||||
|
||||
describe("uniqueSummaryDiffs", () => {
|
||||
test("drops entries without files and preserves unique input", () => {
|
||||
const alpha = diff("alpha.ts", 1)
|
||||
const beta = diff("beta.ts", 1)
|
||||
const invalid = { additions: 1, deletions: 0 } satisfies SnapshotFileDiff
|
||||
const invalid = { additions: 1, deletions: 0 } satisfies FileDiffLegacyInfo
|
||||
|
||||
expect(uniqueSummaryDiffs(undefined)).toEqual([])
|
||||
expect(uniqueSummaryDiffs([])).toEqual([])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SnapshotFileDiff } from "@/types"
|
||||
import type { FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import type { SummaryDiff } from "./timeline-row"
|
||||
|
||||
export function uniqueSummaryDiffs(diffs: SnapshotFileDiff[] | undefined) {
|
||||
export function uniqueSummaryDiffs(diffs: FileDiffLegacyInfo[] | undefined) {
|
||||
const files = new Set<string>()
|
||||
return (diffs ?? [])
|
||||
.reduceRight<SummaryDiff[]>((result, diff) => {
|
||||
@@ -15,6 +15,6 @@ export function uniqueSummaryDiffs(diffs: SnapshotFileDiff[] | undefined) {
|
||||
.reverse()
|
||||
}
|
||||
|
||||
function isSummaryDiff(diff: SnapshotFileDiff): diff is SummaryDiff {
|
||||
function isSummaryDiff(diff: FileDiffLegacyInfo): diff is SummaryDiff {
|
||||
return typeof diff.file === "string"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { SnapshotFileDiff } from "@/types"
|
||||
import type { FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import type { PartGroup } from "@opencode-ai/session-ui/message-part"
|
||||
import { Data, Equal } from "effect"
|
||||
|
||||
export type SummaryDiff = SnapshotFileDiff & { file: string }
|
||||
export type SummaryDiff = FileDiffLegacyInfo & { file: string }
|
||||
|
||||
export namespace TimelineRow {
|
||||
export class TurnGap extends Data.TaggedClass("TurnGap")<{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSDK } from "@/context/sdk"
|
||||
import { Persist, persisted } from "@/utils/persist"
|
||||
import type { SessionStatus } from "@/types"
|
||||
import type { SessionStatus } from "@opencode-ai/client/promise"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useSessionLayout } from "./session-layout"
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import type { Kind } from "@/components/file-tree-v2"
|
||||
import { normalizeFileTreeV2Path } from "@/components/file-tree-v2-model"
|
||||
|
||||
export type RenderDiff = FileDiffInfo | (SnapshotFileDiff & { file: string }) | VcsFileDiff
|
||||
export type RenderDiff = FileDiffInfo | (FileDiffLegacyInfo & { file: string })
|
||||
|
||||
export function normalizePath(p: string) {
|
||||
return normalizeFileTreeV2Path(p)
|
||||
}
|
||||
|
||||
export function filterRenderableDiff(value: FileDiffInfo | SnapshotFileDiff | VcsFileDiff): value is RenderDiff {
|
||||
export function filterRenderableDiff(value: FileDiffInfo | FileDiffLegacyInfo): value is RenderDiff {
|
||||
return typeof value.file === "string"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createMemo, createResource, createSignal, Show, type JSX } from "solid-js"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import {
|
||||
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MAX,
|
||||
SESSION_REVIEW_V2_SIDEBAR_WIDTH_MIN,
|
||||
@@ -31,7 +30,7 @@ import {
|
||||
import type { ReviewPanelV2State } from "@/pages/session/v2/review-panel-v2-state"
|
||||
import { applyFileListKeyDown, SessionFileListV2 } from "@/pages/session/v2/session-file-list-v2"
|
||||
|
||||
type ReviewDiff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
||||
type ReviewDiff = FileDiffInfo | FileDiffLegacyInfo
|
||||
|
||||
export type ReviewPanelV2Props = {
|
||||
title?: JSX.Element
|
||||
|
||||
@@ -1,34 +1,13 @@
|
||||
import type {
|
||||
EventSubscribeOutput,
|
||||
FileDiffInfo,
|
||||
FileDiffLegacyInfo,
|
||||
ProjectListOutput,
|
||||
QuestionAnswer,
|
||||
QuestionInfo,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
SessionInfo,
|
||||
SessionNotFoundError,
|
||||
SessionStatus,
|
||||
SessionV1Info,
|
||||
SessionsResponse,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
|
||||
export type {
|
||||
QuestionAnswer,
|
||||
QuestionRequest,
|
||||
ReferenceInfo,
|
||||
SessionNotFoundError,
|
||||
SessionStatus,
|
||||
}
|
||||
|
||||
export type Project = Omit<ProjectListOutput[number], "canonical"> & { worktree: string }
|
||||
export type Session = Omit<SessionV1Info, "title"> & { title: string }
|
||||
export type SessionV2Info = SessionInfo
|
||||
export type V2SessionListResponse = SessionsResponse
|
||||
export type SnapshotFileDiff = FileDiffLegacyInfo
|
||||
export type VcsFileDiff = FileDiffInfo
|
||||
|
||||
type CurrentEvent = EventSubscribeOutput extends infer Item
|
||||
? Item extends { type: infer Type extends string; data: infer Data }
|
||||
@@ -36,22 +15,10 @@ type CurrentEvent = EventSubscribeOutput extends infer Item
|
||||
: never
|
||||
: never
|
||||
|
||||
export type Event =
|
||||
| Exclude<CurrentEvent, { type: "permission.asked" }>
|
||||
| { type: "permission.asked"; properties: PermissionRequest }
|
||||
export type Event = CurrentEvent
|
||||
|
||||
export type EventSessionError = Extract<Event, { type: "session.error" }>
|
||||
|
||||
export type PermissionRequest = {
|
||||
id: string
|
||||
sessionID: string
|
||||
permission: string
|
||||
patterns: string[]
|
||||
metadata: Record<string, unknown>
|
||||
always: string[]
|
||||
tool?: { messageID: string; callID: string }
|
||||
}
|
||||
|
||||
type MessageError =
|
||||
| { name: "ProviderAuthError"; data: { providerID: string; message: string } }
|
||||
| { name: "UnknownError"; data: { message: string; ref?: string } }
|
||||
@@ -78,7 +45,7 @@ export type UserMessage = {
|
||||
role: "user"
|
||||
time: { created: number }
|
||||
format?: { type: "text" } | { type: "json_schema"; schema: Record<string, unknown>; retryCount?: number }
|
||||
summary?: { title?: string; body?: string; diffs: SnapshotFileDiff[] }
|
||||
summary?: { title?: string; body?: string; diffs: FileDiffLegacyInfo[] }
|
||||
agent: string
|
||||
model: { providerID: string; modelID: string; variant?: string }
|
||||
system?: string
|
||||
@@ -330,9 +297,3 @@ export type Config = {
|
||||
experimental?: Record<string, unknown>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type TextPartInput = Omit<TextPart, "id" | "sessionID" | "messageID"> & { id?: string }
|
||||
export type FilePartInput = Omit<FilePart, "id" | "sessionID" | "messageID"> & { id?: string }
|
||||
export type AgentPartInput = Omit<AgentPart, "id" | "sessionID" | "messageID"> & { id?: string }
|
||||
|
||||
export type Question = QuestionInfo
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { SnapshotFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import type { Message } from "@/types"
|
||||
import { diffs, message } from "./diffs"
|
||||
|
||||
@@ -10,7 +9,7 @@ const item = {
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
status: "modified",
|
||||
} satisfies FileDiffInfo & SnapshotFileDiff
|
||||
} satisfies FileDiffInfo & FileDiffLegacyInfo
|
||||
|
||||
describe("diffs", () => {
|
||||
test("keeps valid arrays", () => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@/types"
|
||||
import type { FileDiffInfo } from "@opencode-ai/client/promise"
|
||||
import type { FileDiffInfo, FileDiffLegacyInfo } from "@opencode-ai/client/promise"
|
||||
import type { Message } from "@/types"
|
||||
|
||||
type Diff = FileDiffInfo | SnapshotFileDiff | VcsFileDiff
|
||||
type Diff = FileDiffInfo | FileDiffLegacyInfo
|
||||
|
||||
function diff(value: unknown): value is Diff {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { SessionNotFoundError } from "@/types"
|
||||
import type { SessionNotFoundError } from "@opencode-ai/client/promise"
|
||||
import type { ConfigInvalidError, ProviderModelNotFoundError } from "./server-errors"
|
||||
import { formatServerError, isSessionNotFoundError, parseReadableConfigInvalidError } from "./server-errors"
|
||||
|
||||
|
||||
@@ -1142,6 +1142,9 @@ export function ContextToolGroup(props: {
|
||||
const running = createMemo(
|
||||
() => partAccessor().state.status === "pending" || partAccessor().state.status === "running",
|
||||
)
|
||||
const showDetails = createMemo(
|
||||
() => !running() || partAccessor().tool === "glob" || partAccessor().tool === "grep",
|
||||
)
|
||||
return (
|
||||
<div data-slot="context-tool-group-item">
|
||||
<div data-component="tool-trigger">
|
||||
@@ -1152,10 +1155,10 @@ export function ContextToolGroup(props: {
|
||||
<span data-slot="basic-tool-tool-title">
|
||||
<TextShimmer text={trigger().title} active={running()} />
|
||||
</span>
|
||||
<Show when={!running() && trigger().subtitle}>
|
||||
<Show when={showDetails() && trigger().subtitle}>
|
||||
<span data-slot="basic-tool-tool-subtitle">{trigger().subtitle}</span>
|
||||
</Show>
|
||||
<Show when={!running() && trigger().args?.length}>
|
||||
<Show when={showDetails() && trigger().args?.length}>
|
||||
<For each={trigger().args}>
|
||||
{(arg) => <span data-slot="basic-tool-tool-arg">{arg}</span>}
|
||||
</For>
|
||||
|
||||
Reference in New Issue
Block a user