chore: generate

This commit is contained in:
opencode-agent[bot]
2026-07-24 02:50:38 +00:00
parent 6faadb332c
commit a46c9aee07
6 changed files with 164 additions and 91 deletions
@@ -124,7 +124,6 @@ describe("bootstrapDirectory", () => {
expect(store.status).toBe("complete") expect(store.status).toBe("complete")
expect(mcpReads).toEqual([]) expect(mcpReads).toEqual([])
}) })
}) })
describe("query keys", () => { describe("query keys", () => {
@@ -1,5 +1,10 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import type { AgentListOutput, ModelDefaultOutput, ModelListOutput, ProviderListOutput } from "@opencode-ai/client/promise" import type {
AgentListOutput,
ModelDefaultOutput,
ModelListOutput,
ProviderListOutput,
} from "@opencode-ai/client/promise"
import { directoryKey, normalizeAgentList, normalizePermissionRequest, normalizeProviderList } from "./utils" import { directoryKey, normalizeAgentList, normalizePermissionRequest, normalizeProviderList } from "./utils"
describe("normalizeAgentList", () => { describe("normalizeAgentList", () => {
@@ -25,7 +25,12 @@ describe("v2 session reducer", () => {
input: { type: "user", delivery: "steer", data: { text: "hello" } }, input: { type: "user", delivery: "steer", data: { text: "hello" } },
}, },
}) })
apply({ ...base, id: "evt_promoted", type: "session.input.promoted", data: { sessionID: "ses_1", inputID: "msg_user" } }) apply({
...base,
id: "evt_promoted",
type: "session.input.promoted",
data: { sessionID: "ses_1", inputID: "msg_user" },
})
apply({ apply({
...base, ...base,
id: "evt_step", id: "evt_step",
@@ -136,12 +141,15 @@ describe("v2 session reducer", () => {
}) })
test("requests hydration when promotion admission was missed", () => { test("requests hydration when promotion admission was missed", () => {
const result = createV2SessionReducer().reduce([], event({ const result = createV2SessionReducer().reduce(
[],
event({
...base, ...base,
id: "evt_promoted", id: "evt_promoted",
type: "session.input.promoted", type: "session.input.promoted",
data: { sessionID: "ses_1", inputID: "msg_user" }, data: { sessionID: "ses_1", inputID: "msg_user" },
})) }),
)
expect(result).toMatchObject({ sessionID: "ses_1", missing: "msg_user", touched: [] }) expect(result).toMatchObject({ sessionID: "ses_1", missing: "msg_user", touched: [] })
}) })
@@ -103,21 +103,34 @@ export function createV2SessionReducer() {
time: { created: event.created }, time: { created: event.created },
}) })
case "session.shell.ended": case "session.shell.ended":
return updateMessage<Shell>(source, (item): item is Shell => item.type === "shell" && item.shellID === event.data.shell.id, (item) => ({ return updateMessage<Shell>(
source,
(item): item is Shell => item.type === "shell" && item.shellID === event.data.shell.id,
(item) => ({
...item, ...item,
status: event.data.shell.status, status: event.data.shell.status,
exit: event.data.shell.exit, exit: event.data.shell.exit,
output: event.data.output, output: event.data.output,
time: { ...item.time, completed: event.created }, time: { ...item.time, completed: event.created },
}), sessionID) }),
sessionID,
)
case "session.step.started": { case "session.step.started": {
const current = source.findLast((item): item is Assistant => item.type === "assistant" && !item.time.completed) const current = source.findLast((item): item is Assistant => item.type === "assistant" && !item.time.completed)
const completed = current && current.id !== event.data.assistantMessageID const completed =
? update(source, current.id, (item) => item.type === "assistant" ? { ...item, retry: undefined, time: { ...item.time, completed: event.created } } : item) current && current.id !== event.data.assistantMessageID
? update(source, current.id, (item) =>
item.type === "assistant"
? { ...item, retry: undefined, time: { ...item.time, completed: event.created } }
: item,
)
: [...source] : [...source]
const existing = completed.find((item) => item.id === event.data.assistantMessageID) const existing = completed.find((item) => item.id === event.data.assistantMessageID)
if (existing?.type === "assistant") if (existing?.type === "assistant")
return result(update(completed, existing.id, (item) => item.type === "assistant" ? { return result(
update(completed, existing.id, (item) =>
item.type === "assistant"
? {
...item, ...item,
agent: event.data.agent, agent: event.data.agent,
model: event.data.model, model: event.data.model,
@@ -126,8 +139,15 @@ export function createV2SessionReducer() {
finish: undefined, finish: undefined,
snapshot: event.data.snapshot ? { ...item.snapshot, start: event.data.snapshot } : item.snapshot, snapshot: event.data.snapshot ? { ...item.snapshot, start: event.data.snapshot } : item.snapshot,
time: { ...item.time, completed: undefined }, time: { ...item.time, completed: undefined },
} : item), current && current.id !== existing.id ? [current.id, existing.id] : [existing.id]) }
return result([...completed, { : item,
),
current && current.id !== existing.id ? [current.id, existing.id] : [existing.id],
)
return result(
[
...completed,
{
id: event.data.assistantMessageID, id: event.data.assistantMessageID,
type: "assistant", type: "assistant",
metadata: event.metadata, metadata: event.metadata,
@@ -136,7 +156,10 @@ export function createV2SessionReducer() {
content: [], content: [],
snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined, snapshot: event.data.snapshot ? { start: event.data.snapshot } : undefined,
time: { created: event.created }, time: { created: event.created },
}], current ? [current.id, event.data.assistantMessageID] : [event.data.assistantMessageID]) },
],
current ? [current.id, event.data.assistantMessageID] : [event.data.assistantMessageID],
)
} }
case "session.step.ended": case "session.step.ended":
return updateAssistant(source, event.data.assistantMessageID, sessionID, (item) => ({ return updateAssistant(source, event.data.assistantMessageID, sessionID, (item) => ({
@@ -144,7 +167,8 @@ export function createV2SessionReducer() {
finish: event.data.finish, finish: event.data.finish,
cost: event.data.cost, cost: event.data.cost,
tokens: event.data.tokens, tokens: event.data.tokens,
snapshot: event.data.snapshot || event.data.files snapshot:
event.data.snapshot || event.data.files
? { ...item.snapshot, end: event.data.snapshot, files: event.data.files } ? { ...item.snapshot, end: event.data.snapshot, files: event.data.files }
: item.snapshot, : item.snapshot,
time: { ...item.time, completed: event.created }, time: { ...item.time, completed: event.created },
@@ -157,7 +181,8 @@ export function createV2SessionReducer() {
retry: undefined, retry: undefined,
cost: event.data.cost ?? item.cost, cost: event.data.cost ?? item.cost,
tokens: event.data.tokens ?? item.tokens, tokens: event.data.tokens ?? item.tokens,
snapshot: event.data.snapshot || event.data.files snapshot:
event.data.snapshot || event.data.files
? { ...item.snapshot, end: event.data.snapshot, files: event.data.files } ? { ...item.snapshot, end: event.data.snapshot, files: event.data.files }
: item.snapshot, : item.snapshot,
time: { ...item.time, completed: event.created }, time: { ...item.time, completed: event.created },
@@ -188,29 +213,46 @@ export function createV2SessionReducer() {
}), }),
})) }))
case "session.reasoning.delta": case "session.reasoning.delta":
return updateContent(source, event.data.assistantMessageID, sessionID, "reasoning", event.data.ordinal, (item) => ({ return updateContent(
source,
event.data.assistantMessageID,
sessionID,
"reasoning",
event.data.ordinal,
(item) => ({
...item, ...item,
text: item.text + event.data.delta, text: item.text + event.data.delta,
})) }),
)
case "session.reasoning.ended": case "session.reasoning.ended":
return updateContent(source, event.data.assistantMessageID, sessionID, "reasoning", event.data.ordinal, (item) => ({ return updateContent(
source,
event.data.assistantMessageID,
sessionID,
"reasoning",
event.data.ordinal,
(item) => ({
...item, ...item,
text: event.data.text, text: event.data.text,
state: event.data.state ?? item.state, state: event.data.state ?? item.state,
time: { created: item.time?.created ?? event.created, completed: event.created }, time: { created: item.time?.created ?? event.created, completed: event.created },
})) }),
)
case "session.tool.input.started": case "session.tool.input.started":
return updateAssistant(source, event.data.assistantMessageID, sessionID, (item) => ({ return updateAssistant(source, event.data.assistantMessageID, sessionID, (item) => ({
...item, ...item,
content: item.content.some((content) => content.type === "tool" && content.id === event.data.callID) content: item.content.some((content) => content.type === "tool" && content.id === event.data.callID)
? item.content ? item.content
: [...item.content, { : [
...item.content,
{
type: "tool", type: "tool",
id: event.data.callID, id: event.data.callID,
name: event.data.name, name: event.data.name,
state: { status: "streaming", input: "" }, state: { status: "streaming", input: "" },
time: { created: event.created }, time: { created: event.created },
}], },
],
})) }))
case "session.tool.input.delta": case "session.tool.input.delta":
return updateTool(source, event.data.assistantMessageID, event.data.callID, sessionID, (tool) => return updateTool(source, event.data.assistantMessageID, event.data.callID, sessionID, (tool) =>
@@ -295,12 +337,21 @@ export function createV2SessionReducer() {
time: { created: event.created }, time: { created: event.created },
}) })
case "session.compaction.delta": case "session.compaction.delta":
return updateMessage<Extract<Compaction, { status: "running" }>>(source, (item): item is Extract<Compaction, { status: "running" }> => item.type === "compaction" && item.status === "running", (item) => ({ return updateMessage<Extract<Compaction, { status: "running" }>>(
source,
(item): item is Extract<Compaction, { status: "running" }> =>
item.type === "compaction" && item.status === "running",
(item) => ({
...item, ...item,
summary: item.summary + event.data.text, summary: item.summary + event.data.text,
}), sessionID) }),
sessionID,
)
case "session.compaction.ended": { case "session.compaction.ended": {
const current = source.findLast((item): item is Extract<Compaction, { status: "running" }> => item.type === "compaction" && item.status === "running") const current = source.findLast(
(item): item is Extract<Compaction, { status: "running" }> =>
item.type === "compaction" && item.status === "running",
)
if (!current) if (!current)
return append({ return append({
id: messageID(event.id), id: messageID(event.id),
@@ -312,16 +363,22 @@ export function createV2SessionReducer() {
recent: event.data.recent, recent: event.data.recent,
time: { created: event.created }, time: { created: event.created },
}) })
return result(update(source, current.id, () => ({ return result(
update(source, current.id, () => ({
...current, ...current,
status: "completed", status: "completed",
reason: event.data.reason, reason: event.data.reason,
summary: event.data.text, summary: event.data.text,
recent: event.data.recent, recent: event.data.recent,
})), [current.id]) })),
[current.id],
)
} }
case "session.compaction.failed": { case "session.compaction.failed": {
const current = source.findLast((item): item is Extract<Compaction, { status: "running" }> => item.type === "compaction" && item.status === "running") const current = source.findLast(
(item): item is Extract<Compaction, { status: "running" }> =>
item.type === "compaction" && item.status === "running",
)
const failed: Extract<Compaction, { status: "failed" }> = { const failed: Extract<Compaction, { status: "failed" }> = {
id: current?.id ?? event.data.inputID ?? messageID(event.id), id: current?.id ?? event.data.inputID ?? messageID(event.id),
type: "compaction", type: "compaction",
@@ -332,7 +389,10 @@ export function createV2SessionReducer() {
time: current?.time ?? { created: event.created }, time: current?.time ?? { created: event.created },
} }
if (!current) return append(failed) if (!current) return append(failed)
return result(update(source, current.id, () => failed), [failed.id]) return result(
update(source, current.id, () => failed),
[failed.id],
)
} }
default: default:
return return
@@ -362,7 +422,7 @@ function update(
id: string, id: string,
apply: (item: SessionMessageInfo) => SessionMessageInfo, apply: (item: SessionMessageInfo) => SessionMessageInfo,
) { ) {
return source.map((item) => item.id === id ? apply(item) : item) return source.map((item) => (item.id === id ? apply(item) : item))
} }
function updateMessage<T extends SessionMessageInfo>( function updateMessage<T extends SessionMessageInfo>(
@@ -373,7 +433,11 @@ function updateMessage<T extends SessionMessageInfo>(
): V2SessionReduction { ): V2SessionReduction {
const current = source.findLast(matches) const current = source.findLast(matches)
if (!current) return { sessionID, messages: [...source], touched: [] } if (!current) return { sessionID, messages: [...source], touched: [] }
return { sessionID, messages: update(source, current.id, (item) => matches(item) ? apply(item) : item), touched: [current.id] } return {
sessionID,
messages: update(source, current.id, (item) => (matches(item) ? apply(item) : item)),
touched: [current.id],
}
} }
function updateAssistant( function updateAssistant(
@@ -384,7 +448,7 @@ function updateAssistant(
): V2SessionReduction { ): V2SessionReduction {
return { return {
sessionID, sessionID,
messages: update(source, id, (item) => item.type === "assistant" ? apply(item) : item), messages: update(source, id, (item) => (item.type === "assistant" ? apply(item) : item)),
touched: source.some((item) => item.id === id && item.type === "assistant") ? [id] : [], touched: source.some((item) => item.id === id && item.type === "assistant") ? [id] : [],
} }
} }
@@ -395,7 +459,9 @@ function updateContent<T extends "text" | "reasoning">(
sessionID: string, sessionID: string,
type: T, type: T,
ordinal: number, ordinal: number,
apply: (item: Extract<Assistant["content"][number], { type: T }>) => Extract<Assistant["content"][number], { type: T }>, apply: (
item: Extract<Assistant["content"][number], { type: T }>,
) => Extract<Assistant["content"][number], { type: T }>,
) { ) {
return updateAssistant(source, messageID, sessionID, (assistant) => { return updateAssistant(source, messageID, sessionID, (assistant) => {
let index = -1 let index = -1
@@ -414,11 +480,13 @@ function updateTool(
messageID: string, messageID: string,
callID: string, callID: string,
sessionID: string, sessionID: string,
apply: (item: Extract<Assistant["content"][number], { type: "tool" }>) => Extract<Assistant["content"][number], { type: "tool" }>, apply: (
item: Extract<Assistant["content"][number], { type: "tool" }>,
) => Extract<Assistant["content"][number], { type: "tool" }>,
) { ) {
return updateAssistant(source, messageID, sessionID, (assistant) => ({ return updateAssistant(source, messageID, sessionID, (assistant) => ({
...assistant, ...assistant,
content: assistant.content.map((item) => item.type === "tool" && item.id === callID ? apply(item) : item), content: assistant.content.map((item) => (item.type === "tool" && item.id === callID ? apply(item) : item)),
})) }))
} }
+1 -6
View File
@@ -11,12 +11,7 @@ import type {
import { QueryClient } from "@tanstack/solid-query" import { QueryClient } from "@tanstack/solid-query"
import { canDisposeDirectory, pickDirectoriesToEvict } from "./global-sync/eviction" import { canDisposeDirectory, pickDirectoriesToEvict } from "./global-sync/eviction"
import { estimateRootSessionTotal, loadRootSessions } from "./global-sync/session-load" import { estimateRootSessionTotal, loadRootSessions } from "./global-sync/session-load"
import { import { loadActiveSessionsQuery, loadMcpQuery, loadMcpResourcesQuery, seedActiveSessionStatuses } from "./server-sync"
loadActiveSessionsQuery,
loadMcpQuery,
loadMcpResourcesQuery,
seedActiveSessionStatuses,
} from "./server-sync"
import { ServerScope } from "@/utils/server-scope" import { ServerScope } from "@/utils/server-scope"
import { createServerSession } from "./server-session" import { createServerSession } from "./server-session"
@@ -122,9 +122,7 @@ describe("normalizeSessionMessages", () => {
expect.objectContaining({ id: "msg_shell", role: "user" }), expect.objectContaining({ id: "msg_shell", role: "user" }),
expect.objectContaining({ id: "msg_shell:assistant", role: "assistant", parentID: "msg_shell" }), expect.objectContaining({ id: "msg_shell:assistant", role: "assistant", parentID: "msg_shell" }),
]) ])
expect(result.parts.get("msg_shell")).toEqual([ expect(result.parts.get("msg_shell")).toEqual([expect.objectContaining({ type: "text", text: "printf hello" })])
expect.objectContaining({ type: "text", text: "printf hello" }),
])
expect(result.parts.get("msg_shell:assistant")).toEqual([ expect(result.parts.get("msg_shell:assistant")).toEqual([
expect.objectContaining({ expect.objectContaining({
type: "tool", type: "tool",