chore(app): vendor v2 client snapshot (#38818)
This commit is contained in:
parent
2b2b69d668
commit
0a6637e17a
33 changed files with 268 additions and 191 deletions
|
|
@ -134,7 +134,8 @@ export const createDirSyncContext = (
|
|||
},
|
||||
more: createMemo(() => current()[0].session.length >= current()[0].limit),
|
||||
archive: async (sessionID: string) => {
|
||||
await serverSDK.api.session.archive({ sessionID, directory })
|
||||
if ((await serverSDK.protocol) !== "v1") return
|
||||
await serverSDK.client.session.update({ sessionID, directory, time: { archived: Date.now() } })
|
||||
current()[1](
|
||||
"session",
|
||||
produce((draft) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
|
|||
import { createStore } from "solid-js/store"
|
||||
import { QueryClient } from "@tanstack/solid-query"
|
||||
import type { Config, OpencodeClient, Project } from "@opencode-ai/sdk/v2/client"
|
||||
import type { AgentApi, CatalogApi, CommandApi, ProjectApi, ReferenceApi } from "@opencode-ai/client/promise"
|
||||
import type { AgentApi, CatalogApi, CommandApi, ReferenceApi } from "@opencode-ai/client/promise"
|
||||
import type { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
||||
import {
|
||||
bootstrapDirectory,
|
||||
|
|
@ -17,6 +17,8 @@ import type { State, VcsCache } from "./types"
|
|||
import { ServerScope } from "@/utils/server-scope"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type ProjectApi = ServerApi["project"]
|
||||
|
||||
const provider = { all: new Map(), connected: [], default: {} } satisfies NormalizedProviderListResponse
|
||||
const api = {
|
||||
agent: { list: async () => ({ location: {}, data: [] }) },
|
||||
|
|
@ -200,7 +202,7 @@ describe("query keys", () => {
|
|||
calls.push(input)
|
||||
return {
|
||||
location: {},
|
||||
data: [{ name: "review", template: "Review files", source: "command" as const }],
|
||||
data: [{ name: "review", template: "Review files" /* source: "command" as const */ }],
|
||||
}
|
||||
},
|
||||
} as unknown as CommandApi
|
||||
|
|
@ -208,7 +210,7 @@ describe("query keys", () => {
|
|||
const result = await loadCommands("/repo", api)
|
||||
|
||||
expect(calls).toEqual([{ location: { directory: "/repo" } }])
|
||||
expect(result).toEqual([{ name: "review", template: "Review files", source: "command" }])
|
||||
expect(result).toEqual([{ name: "review", template: "Review files" /* source: "command" */ }])
|
||||
})
|
||||
|
||||
test("loads projects from the current endpoint", async () => {
|
||||
|
|
|
|||
|
|
@ -16,18 +16,12 @@ import type {
|
|||
CommandInfo,
|
||||
CommandListInput,
|
||||
CommandListOutput,
|
||||
McpApi,
|
||||
PathGetInput,
|
||||
PathGetOutput,
|
||||
PermissionApi,
|
||||
ProjectCurrentInput,
|
||||
ProjectCurrentOutput,
|
||||
ProjectListOutput,
|
||||
QuestionApi,
|
||||
ReferenceListInput,
|
||||
ReferenceListOutput,
|
||||
SessionApi,
|
||||
VcsApi,
|
||||
} from "@opencode-ai/client/promise"
|
||||
import { showToast } from "@/utils/toast"
|
||||
import { getFilename } from "@opencode-ai/core/util/path"
|
||||
|
|
@ -50,6 +44,7 @@ import { NormalizedProviderListResponse } from "@opencode-ai/session-ui/context"
|
|||
import { ScopedKey, type ServerScope } from "@/utils/server-scope"
|
||||
import { normalizeSessionInfo } from "@/utils/session"
|
||||
import type { ServerProtocol } from "@/utils/server-protocol"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type GlobalStore = {
|
||||
ready: boolean
|
||||
|
|
@ -121,9 +116,10 @@ type ProjectApi = {
|
|||
readonly current: (input?: ProjectCurrentInput) => Promise<ProjectCurrentOutput>
|
||||
}
|
||||
|
||||
type PathApi = {
|
||||
readonly get: (input?: PathGetInput) => Promise<PathGetOutput>
|
||||
}
|
||||
type McpApi = ServerApi["mcp"]
|
||||
type PermissionApi = ServerApi["permission"]
|
||||
type QuestionApi = ServerApi["question"]
|
||||
type VcsApi = ServerApi["vcs"]
|
||||
|
||||
export const loadProjectsQuery = (scope: ServerScope, api: ProjectApi) =>
|
||||
queryOptions({
|
||||
|
|
@ -143,7 +139,7 @@ export const loadProjectsQuery = (scope: ServerScope, api: ProjectApi) =>
|
|||
|
||||
export async function bootstrapGlobal(input: {
|
||||
serverSDK: OpencodeClient
|
||||
serverAPI: CatalogApi & { readonly path: PathApi; readonly project: ProjectApi }
|
||||
serverAPI: CatalogApi & { readonly project: ProjectApi }
|
||||
protocol?: Promise<ServerProtocol>
|
||||
scope: ServerScope
|
||||
requestFailedTitle: string
|
||||
|
|
@ -158,7 +154,7 @@ export async function bootstrapGlobal(input: {
|
|||
input.queryClient.fetchQuery(
|
||||
loadProvidersQuery(input.scope, null, input.serverAPI, input.serverSDK, input.protocol),
|
||||
),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverAPI.path)),
|
||||
() => input.queryClient.fetchQuery(loadPathQuery(input.scope, null, input.serverSDK, input.protocol)),
|
||||
() =>
|
||||
input.queryClient
|
||||
.fetchQuery(loadProjectsQuery(input.scope, input.serverAPI.project))
|
||||
|
|
@ -289,17 +285,26 @@ export const loadCommands = (
|
|||
agent: command.agent,
|
||||
model: providerID && id ? { providerID, id } : undefined,
|
||||
subtask: command.subtask,
|
||||
source: command.source === "skill" ? undefined : command.source,
|
||||
// source: command.source === "skill" ? undefined : command.source,
|
||||
}
|
||||
})
|
||||
}
|
||||
return api.list({ location: { directory } }).then((result) => result.data)
|
||||
})
|
||||
|
||||
export const loadPathQuery = (scope: ServerScope, directory: string | null, api: PathApi) =>
|
||||
export const loadPathQuery = (
|
||||
scope: ServerScope,
|
||||
directory: string | null,
|
||||
sdk: OpencodeClient,
|
||||
protocol?: Promise<ServerProtocol>,
|
||||
) =>
|
||||
queryOptions<Path>({
|
||||
queryKey: [scope, directory, "path"],
|
||||
queryFn: () => retry(() => api.get(directory ? { location: { directory } } : undefined)),
|
||||
queryFn: async () => {
|
||||
if ((await protocol) !== "v1")
|
||||
return { state: "", config: "", worktree: "", directory: directory ?? "", home: "" }
|
||||
return retry(() => sdk.path.get({ directory: directory ?? undefined }).then((result) => result.data!))
|
||||
},
|
||||
})
|
||||
|
||||
export const loadReferencesQuery = (
|
||||
|
|
@ -328,7 +333,6 @@ export async function bootstrapDirectory(input: {
|
|||
readonly agent: AgentListApi
|
||||
readonly command: CommandListApi
|
||||
readonly mcp: McpApi
|
||||
readonly path: PathApi
|
||||
readonly permission: PermissionApi
|
||||
readonly project: ProjectApi
|
||||
readonly question: QuestionApi
|
||||
|
|
@ -408,19 +412,20 @@ export async function bootstrapDirectory(input: {
|
|||
!seededPath &&
|
||||
(() =>
|
||||
input.queryClient
|
||||
.ensureQueryData(loadPathQuery(input.scope, input.directory, input.api.path))
|
||||
.ensureQueryData(loadPathQuery(input.scope, input.directory, input.sdk, input.protocol))
|
||||
.then((data) => {
|
||||
const next = projectID(data.directory ?? input.directory, input.global.project)
|
||||
if (next) input.setStore("project", next)
|
||||
})),
|
||||
() =>
|
||||
retry(() =>
|
||||
input.api.vcs.get({ location: { directory: input.directory } }).then((result) => {
|
||||
const next = { branch: result.data.branch, default_branch: result.data.defaultBranch }
|
||||
retry(async () => {
|
||||
if ((await input.protocol) !== "v1") return
|
||||
return input.sdk.vcs.get().then((result) => {
|
||||
const next = { branch: result.data?.branch, default_branch: result.data?.default_branch }
|
||||
input.setStore("vcs", next)
|
||||
if (next) input.vcsCache.setStore("value", next)
|
||||
}),
|
||||
),
|
||||
})
|
||||
}),
|
||||
input.mcp &&
|
||||
(() =>
|
||||
loadCommands(input.directory, input.api.command, input.sdk, input.protocol).then((commands) =>
|
||||
|
|
|
|||
|
|
@ -211,19 +211,19 @@ export function applyDirectoryEvent(input: {
|
|||
}))
|
||||
break
|
||||
}
|
||||
case "session.archived": {
|
||||
const properties = event.properties as { sessionID: string }
|
||||
const result = Binary.search(input.store.session, properties.sessionID, (session) => session.id)
|
||||
if (!result.found) break
|
||||
const info = input.store.session[result.index]
|
||||
input.setStore(
|
||||
"session",
|
||||
produce((draft) => void draft.splice(result.index, 1)),
|
||||
)
|
||||
cleanupSessionCaches(input.setStore, properties.sessionID)
|
||||
if (!info?.parentID) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||
break
|
||||
}
|
||||
// case "session.archived": {
|
||||
// const properties = event.properties as { sessionID: string }
|
||||
// const result = Binary.search(input.store.session, properties.sessionID, (session) => session.id)
|
||||
// if (!result.found) break
|
||||
// const info = input.store.session[result.index]
|
||||
// input.setStore(
|
||||
// "session",
|
||||
// produce((draft) => void draft.splice(result.index, 1)),
|
||||
// )
|
||||
// cleanupSessionCaches(input.setStore, properties.sessionID)
|
||||
// if (!info?.parentID) input.setStore("sessionTotal", (value) => Math.max(0, value - 1))
|
||||
// break
|
||||
// }
|
||||
case "session.moved": {
|
||||
const properties = event.properties as {
|
||||
sessionID: string
|
||||
|
|
|
|||
|
|
@ -570,16 +570,22 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||
continue
|
||||
}
|
||||
|
||||
void serverSdk()
|
||||
.api.project.update({ projectID: project.id, icon: { color } })
|
||||
.then((result) =>
|
||||
serverSync().set("project", (items) =>
|
||||
items.map((item) => (item.id === result.id ? normalizeProjectInfo(result) : item)),
|
||||
),
|
||||
)
|
||||
.catch(() => {
|
||||
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||
})
|
||||
const projectID = project.id
|
||||
void (async () => {
|
||||
const sdk = serverSdk()
|
||||
if ((await sdk.protocol) !== "v1") return
|
||||
return sdk.client.project
|
||||
.update({ projectID, directory: worktree, icon: { color } })
|
||||
.then((response) => response.data)
|
||||
.then((result) => {
|
||||
if (!result) return
|
||||
serverSync().set("project", (items) =>
|
||||
items.map((item) => (item.id === result.id ? normalizeProjectInfo(result) : item)),
|
||||
)
|
||||
})
|
||||
})().catch(() => {
|
||||
if (colorRequested.get(worktree) === color) colorRequested.delete(worktree)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ describe("v2 session reducer", () => {
|
|||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
callID: "call_1",
|
||||
structured: {},
|
||||
metadata: {},
|
||||
content: [{ type: "text", text: "done" }],
|
||||
executed: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -269,13 +269,18 @@ export function createV2SessionReducer() {
|
|||
...tool,
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
// structured: {}, content: []
|
||||
state: { status: "running", input: event.data.input, metadata: {} },
|
||||
time: { ...tool.time, ran: event.created },
|
||||
}))
|
||||
case "session.tool.progress":
|
||||
return updateTool(source, event.data.assistantMessageID, event.data.callID, sessionID, (tool) =>
|
||||
tool.state.status === "running"
|
||||
? { ...tool, state: { ...tool.state, structured: event.data.structured, content: event.data.content } }
|
||||
? {
|
||||
...tool,
|
||||
// state: { ...tool.state, structured: event.data.structured, content: event.data.content },
|
||||
state: { ...tool.state, metadata: event.data.metadata },
|
||||
}
|
||||
: tool,
|
||||
)
|
||||
case "session.tool.success":
|
||||
|
|
@ -288,9 +293,10 @@ export function createV2SessionReducer() {
|
|||
state: {
|
||||
status: "completed",
|
||||
input: tool.state.input,
|
||||
structured: event.data.structured,
|
||||
// structured: event.data.structured,
|
||||
metadata: event.data.metadata,
|
||||
content: event.data.content,
|
||||
result: event.data.result,
|
||||
// result: event.data.result,
|
||||
},
|
||||
time: { ...tool.time, completed: event.created },
|
||||
}
|
||||
|
|
@ -305,10 +311,11 @@ export function createV2SessionReducer() {
|
|||
state: {
|
||||
status: "error",
|
||||
input: typeof tool.state.input === "string" ? {} : tool.state.input,
|
||||
structured: tool.state.status === "running" ? tool.state.structured : {},
|
||||
content: tool.state.status === "running" ? tool.state.content : [],
|
||||
// structured: tool.state.status === "running" ? tool.state.structured : {},
|
||||
metadata: event.data.metadata ?? (tool.state.status === "running" ? tool.state.metadata : {}),
|
||||
content: event.data.content,
|
||||
error: event.data.error,
|
||||
result: event.data.result,
|
||||
// result: event.data.result,
|
||||
},
|
||||
time: { ...tool.time, completed: event.created },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import type { retry } from "@opencode-ai/core/util/retry"
|
||||
import type { MessageApi, OpenCodeEvent, SessionApi } from "@opencode-ai/client/promise"
|
||||
import type { OpenCodeEvent, SessionApi } from "@opencode-ai/client/promise"
|
||||
import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2/client"
|
||||
import { createServerSession } from "./server-session"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type MessageApi = ServerApi["message"]
|
||||
|
||||
const session = (id: string, parentID?: string): Session => ({
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Binary } from "@opencode-ai/core/util/binary"
|
||||
import { retry } from "@opencode-ai/core/util/retry"
|
||||
import type { MessageApi, OpenCodeEvent, SessionApi, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type { OpenCodeEvent, SessionApi, SessionMessageInfo } from "@opencode-ai/client/promise"
|
||||
import type {
|
||||
Message,
|
||||
OpencodeClient,
|
||||
|
|
@ -21,6 +21,9 @@ import { normalizeSessionInfo } from "@/utils/session"
|
|||
import { normalizeSessionMessages } from "@/utils/session-message"
|
||||
import { dropSessionCaches, pickSessionCacheEvictions, SESSION_CACHE_LIMIT } from "./global-sync/session-cache"
|
||||
import { createV2SessionReducer, type V2SessionReduction } from "./server-session-v2-reducer"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type MessageApi = ServerApi["message"]
|
||||
|
||||
const cmp = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0)
|
||||
const cmpMessage = (a: Message, b: Message) => a.time.created - b.time.created || cmp(a.id, b.id)
|
||||
|
|
@ -954,10 +957,10 @@ export function createServerSession(
|
|||
})
|
||||
if (event.type === "session.usage.updated" && info)
|
||||
remember({ ...info, cost: event.data.cost, tokens: event.data.tokens })
|
||||
if (event.type === "session.archived") {
|
||||
if (info) remember({ ...info, time: { ...info.time, archived: event.created, updated: event.created } })
|
||||
evict([sessionID])
|
||||
}
|
||||
// if (event.type === "session.archived") {
|
||||
// if (info) remember({ ...info, time: { ...info.time, archived: event.created, updated: event.created } })
|
||||
// evict([sessionID])
|
||||
// }
|
||||
if (event.type === "session.execution.started") setData("session_status", sessionID, { type: "busy" })
|
||||
if (
|
||||
event.type === "session.execution.succeeded" ||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client"
|
||||
import type {
|
||||
McpApi,
|
||||
McpListInput,
|
||||
McpResourceCatalogInput,
|
||||
SessionApi,
|
||||
|
|
@ -14,6 +13,9 @@ import { estimateRootSessionTotal, loadRootSessions } from "./global-sync/sessio
|
|||
import { loadActiveSessionsQuery, loadMcpQuery, loadMcpResourcesQuery, seedActiveSessionStatuses } from "./server-sync"
|
||||
import { ServerScope } from "@/utils/server-scope"
|
||||
import { createServerSession } from "./server-session"
|
||||
import type { ServerApi } from "@/utils/server"
|
||||
|
||||
type McpApi = ServerApi["mcp"]
|
||||
|
||||
describe("MCP queries", () => {
|
||||
test("loads current servers for the requested location", async () => {
|
||||
|
|
|
|||
|
|
@ -188,7 +188,8 @@ function makeQueryOptionsApi(
|
|||
projects: () => loadProjectsQuery(scope, serverAPI.project),
|
||||
providers: (directory: PathKey | null) =>
|
||||
loadProvidersQuery(scope, directory, serverAPI, directory ? sdkFor(directory) : serverSDK(), protocol),
|
||||
path: (directory: PathKey | null) => loadPathQuery(scope, directory, serverAPI.path),
|
||||
path: (directory: PathKey | null) =>
|
||||
loadPathQuery(scope, directory, directory ? sdkFor(directory) : serverSDK(), protocol),
|
||||
agents: (directory: PathKey) => loadAgentsQuery(scope, directory, serverAPI.agent, sdkFor(directory), protocol),
|
||||
references: (directory: PathKey) =>
|
||||
loadReferencesQuery(scope, directory, serverAPI.reference, sdkFor(directory), protocol),
|
||||
|
|
@ -581,7 +582,7 @@ export function createServerSyncContextInner(serverSDK: ServerSDK) {
|
|||
children.mark(key)
|
||||
if (
|
||||
event.current?.type === "session.moved" ||
|
||||
event.current?.type === "session.archived" ||
|
||||
// event.current?.type === "session.archived" ||
|
||||
event.current?.type === "session.forked" ||
|
||||
eventType === "command.updated" ||
|
||||
eventType === "config.updated" ||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue