Compare commits

...

13 Commits

Author SHA1 Message Date
𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 c9e1aedd42 fix(desktop): handle moved projects and deleted paths 2026-06-27 02:33:26 +00:00
opencode-agent[bot] 4b948c5d74 fix(app): hide home session archive action (#34136)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-27 02:23:36 +00:00
opencode-agent[bot] cd56c51e2d fix(app): keep bare slash as plain inline code (#34122)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-26 22:21:01 +00:00
opencode-agent[bot] 43e39d7f68 fix(tui): use generated event union (#34118)
Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local>
2026-06-26 18:04:53 -04:00
Frank 7a17925495 zen: new inference
deploy / deploy (push) Has been cancelled
2026-06-26 16:25:07 -04:00
Brendan Allan 5acb2530b4 fix(app): centralize notification state (#34105) 2026-06-26 19:24:33 +00:00
opencode-agent[bot] 44a6787359 chore: generate 2026-06-26 19:18:18 +00:00
Kit Langton 42e6b7db32 feat(sdk): expose live event stream (#34098) 2026-06-26 21:16:33 +02:00
opencode-agent[bot] 2c02f8bace chore: update nix node_modules hashes 2026-06-26 18:53:34 +00:00
Affan Ali 2ec20e576b fix(app): slow tooltip display for models (#30745)
Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com>
2026-06-27 02:53:08 +08:00
opencode-agent[bot] 20f47fec7a chore: generate 2026-06-26 18:51:42 +00:00
Kit Langton 65210f2d97 feat(api): add finite durable session history pages (#34097) 2026-06-26 14:49:47 -04:00
Brendan Allan af0b7ffae7 fix(app): animate prompt selectors after loading (#34101) 2026-06-27 02:36:01 +08:00
56 changed files with 3986 additions and 5360 deletions
+4
View File
@@ -83,6 +83,7 @@
"@types/luxon": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"tw-animate-css": "1.4.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
@@ -990,6 +991,7 @@
"@typescript/native-preview": "catalog:",
"solid-js": "catalog:",
"tailwindcss": "catalog:",
"tw-animate-css": "1.4.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
@@ -5315,6 +5317,8 @@
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
"tw-to-css": ["tw-to-css@0.0.12", "", { "dependencies": { "postcss": "8.4.31", "postcss-css-variables": "0.18.0", "tailwindcss": "3.3.2" } }, "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-OiWvZ57vuyHwiIKNtW1n1KX+MLmOXVG3x4fLKvUoGQw=",
"aarch64-linux": "sha256-RnPLxVEg/UsL5IeIFWmXMSLUOG6rVrajYxhyDYj1vTA=",
"aarch64-darwin": "sha256-KPIgcBA0pTFBPrCTSZgIbvEorbtWcMgXvyX9bFAypVs=",
"x86_64-darwin": "sha256-6jVU7/uVId0VD24MVQ8s8Ill5b6PsKdlBgHg+oceKRg="
"x86_64-linux": "sha256-rB/CcrBUQVZ08nBFSYA8u2w88rQmTpKxKPkIreDEKgI=",
"aarch64-linux": "sha256-ZRTphtic8Ip96MnILteFgZAUxjK9O4YfJu2O6u/0H8k=",
"aarch64-darwin": "sha256-VK5XIzraP0HtqnPwPCejiDKer4ewtNtX1vxP5uuyjSk=",
"x86_64-darwin": "sha256-ZLPHqcCZB1EmxQk95cmUpiODTTKOyi7PSF0yr/rDk6Y="
}
}
+1
View File
@@ -38,6 +38,7 @@
"@types/luxon": "catalog:",
"@types/node": "catalog:",
"@typescript/native-preview": "catalog:",
"tw-animate-css": "1.4.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
+22 -12
View File
@@ -38,7 +38,7 @@ import { HighlightsProvider } from "@/context/highlights"
import { LanguageProvider, type Locale, useLanguage } from "@/context/language"
import { LayoutProvider } from "@/context/layout"
import { ModelsProvider } from "@/context/models"
import { NotificationProvider } from "@/context/notification"
import { NotificationProvider, useNotification } from "@/context/notification"
import { PermissionProvider } from "@/context/permission"
import { PromptProvider } from "@/context/prompt"
import { ServerConnection, ServerProvider, serverName, useServer } from "@/context/server"
@@ -316,9 +316,7 @@ function ServerScopedProviders(props: ServerScopedShellProps) {
return (
<PermissionProvider directory={props.directory}>
<LayoutProvider>
<NotificationProvider directory={props.directory} sessionID={props.sessionID}>
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
</NotificationProvider>
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
</LayoutProvider>
</PermissionProvider>
)
@@ -345,13 +343,23 @@ function NewAppLayout(props: ParentProps) {
function TargetServerScopedProviders(props: ServerScopedShellProps) {
return (
<PermissionProvider directory={props.directory}>
<NotificationProvider directory={props.directory} sessionID={props.sessionID}>
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
</NotificationProvider>
<MarkSessionNotificationsViewed sessionID={props.sessionID} />
<ModelsProvider directory={props.directory}>{props.children}</ModelsProvider>
</PermissionProvider>
)
}
function MarkSessionNotificationsViewed(props: { sessionID?: () => string | undefined }) {
const notification = useNotification()
createEffect(() => {
const sessionID = props.sessionID?.()
if (!notification.ready() || !sessionID) return
if (notification.session.unseenCount(sessionID) === 0) return
notification.session.markViewed(sessionID)
})
return null
}
function SessionProviders(props: ParentProps) {
return (
<TerminalProvider>
@@ -560,11 +568,13 @@ export function AppInterface(props: {
component={props.router ?? Router}
root={(routerProps) => (
<TabsProvider>
<ServerShell>
<Show when={useSettings().general.newLayoutDesigns()} fallback={routerProps.children}>
<NewAppLayout>{routerProps.children}</NewAppLayout>
</Show>
</ServerShell>
<NotificationProvider>
<ServerShell>
<Show when={useSettings().general.newLayoutDesigns()} fallback={routerProps.children}>
<NewAppLayout>{routerProps.children}</NewAppLayout>
</Show>
</ServerShell>
</NotificationProvider>
</TabsProvider>
)}
>
@@ -59,6 +59,7 @@ const ModelList: Component<{
class="w-full"
placement="right-start"
gutter={12}
openDelay={0}
value={<ModelTooltip model={item} latest={item.latest} free={isFree(item.provider.id, item.cost)} />}
>
{node}
@@ -40,6 +40,7 @@ beforeAll(async () => {
describe("file tree fetch discipline", () => {
test("root lists on mount unless already loaded or loading", () => {
expect(shouldListRoot({ level: 0 })).toBe(true)
expect(shouldListRoot({ level: 0, filtered: true })).toBe(false)
expect(shouldListRoot({ level: 0, dir: { loaded: true } })).toBe(false)
expect(shouldListRoot({ level: 0, dir: { loading: true } })).toBe(false)
expect(shouldListRoot({ level: 1 })).toBe(false)
+11 -4
View File
@@ -32,7 +32,12 @@ type Filter = {
dirs: Set<string>
}
export function shouldListRoot(input: { level: number; dir?: { loaded?: boolean; loading?: boolean } }) {
export function shouldListRoot(input: {
level: number
filtered?: boolean
dir?: { loaded?: boolean; loading?: boolean }
}) {
if (input.filtered) return false
if (input.level !== 0) return false
if (input.dir?.loaded) return false
if (input.dir?.loading) return false
@@ -309,7 +314,7 @@ export default function FileTree(props: {
filter: current,
expanded: (dir) => untrack(() => file.tree.state(dir)?.expanded) ?? false,
})
for (const dir of dirs) file.tree.expand(dir)
for (const dir of dirs) file.tree.expand(dir, { load: false })
})
createEffect(
@@ -317,7 +322,7 @@ export default function FileTree(props: {
() => props.path,
(path) => {
const dir = untrack(() => file.tree.state(path))
if (!shouldListRoot({ level, dir })) return
if (!shouldListRoot({ level, filtered: !!filter(), dir })) return
void file.tree.list(path)
},
{ defer: false },
@@ -401,7 +406,9 @@ export default function FileTree(props: {
data-scope="filetree"
forceMount={false}
open={expanded()}
onOpenChange={(open) => (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))}
onOpenChange={(open) =>
open ? file.tree.expand(node.path, { load: !filter() }) : file.tree.collapse(node.path)
}
>
<Collapsible.Trigger>
<FileTreeNode
+11 -6
View File
@@ -1343,9 +1343,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
const agentsLoading = () => props.controls.agents.loading
const agentsShouldFadeIn = createMemo((prev) => prev ?? agentsLoading())
const agentsShouldFadeIn = createMemo<boolean>((prev) => prev ?? agentsLoading())
const providersLoading = () => props.controls.model.loading
const providersShouldFadeIn = createMemo((prev) => prev ?? providersLoading())
const providersShouldFadeIn = createMemo<boolean>((prev) => prev ?? providersLoading())
const [promptReady] = createResource(
() => prompt.ready.promise,
@@ -1359,6 +1359,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const modelControlState = createMemo<ComposerModelControlState>(() => ({
loading: providersLoading(),
shouldAnimate: providersShouldFadeIn(),
paid: props.controls.model.paid,
title: language.t("command.model.choose"),
keybind: command.keybind("model.choose"),
@@ -1519,10 +1520,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</Show>
{props.toolbar}
<ComposerModelControl state={modelControlState()} />
<Show when={store.mode !== "shell" && showVariantControl()}>
<Show when={!providersLoading() && store.mode !== "shell" && showVariantControl()}>
<div
data-component="prompt-variant-control"
classList={{
"animate-in fade-in": providersShouldFadeIn(),
"hidden group-hover/prompt-input:block group-focus-within/prompt-input:block":
!props.controls.model.selection.variant.current() && !store.variantOpen,
}}
@@ -1765,7 +1767,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={!agentsLoading()}>
<div
data-component="prompt-agent-control"
style={agentsShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
classList={{ "animate-in fade-in duration-300": agentsShouldFadeIn() }}
>
<TooltipKeybind
placement="top"
@@ -1794,7 +1796,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={store.mode !== "shell"}>
<div
data-component="prompt-model-control"
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
classList={{ "animate-in fade-in duration-300": providersShouldFadeIn() }}
>
<Show
when={props.controls.model.paid}
@@ -1873,7 +1875,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={showVariantControl()}>
<div
data-component="prompt-variant-control"
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}
classList={{ "animate-in fade-in duration-300": providersShouldFadeIn() }}
>
<TooltipKeybind
placement="top"
@@ -1923,6 +1925,7 @@ type ComposerAgentControlState = {
type ComposerModelControlState = {
loading: boolean
shouldAnimate: boolean
paid: boolean
title: string
keybind: string
@@ -1970,6 +1973,7 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
variant="ghost"
size="normal"
class="min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group"
classList={{ "animate-in fade-in": props.state.shouldAnimate }}
style={props.state.style}
onClick={props.state.onUnpaidClick}
>
@@ -2000,6 +2004,7 @@ function ComposerModelControl(props: { state: ComposerModelControlState }) {
style: props.state.style,
class:
"min-w-0 max-w-[220px] justify-start text-[13px] font-[440] leading-5 text-v2-text-text-faint group",
classList: { "animate-in fade-in": props.state.shouldAnimate },
"data-action": "prompt-model",
}}
onClose={props.state.onClose}
@@ -0,0 +1,28 @@
import { describe, expect, test } from "bun:test"
import { createRoot } from "solid-js"
import { createFileTreeStore } from "./tree-store"
describe("file tree store", () => {
test("expands synthetic directories without listing them", () => {
const listed: string[] = []
const value = createRoot((dispose) => ({
dispose,
tree: createFileTreeStore({
scope: () => "/project",
normalizeDir: (input) => input,
list: (input) => {
listed.push(input)
return Promise.resolve([])
},
onError: () => undefined,
}),
}))
value.tree.expandDir("deleted/parent", { load: false })
expect(value.tree.dirState("deleted/parent")?.expanded).toBe(true)
expect(listed).toEqual([])
value.dispose()
})
})
+2 -1
View File
@@ -127,10 +127,11 @@ export function createFileTreeStore(options: TreeStoreOptions) {
return promise
}
const expandDir = (input: string) => {
const expandDir = (input: string, opts?: { load?: boolean }) => {
const dir = options.normalizeDir(input)
ensureDir(dir)
setTree("dir", dir, "expanded", true)
if (opts?.load === false) return
void listDir(dir)
}
+343 -245
View File
@@ -1,9 +1,9 @@
import { createStore, reconcile } from "solid-js/store"
import { type Accessor, batch, createEffect, createMemo, onCleanup } from "solid-js"
import { useParams } from "@solidjs/router"
import { type Accessor, batch, createEffect, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
import { useParams, useSearchParams } from "@solidjs/router"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useServerSDK } from "./server-sdk"
import { useServerSync } from "./server-sync"
import type { ServerSDK } from "./server-sdk"
import type { ServerSync } from "./server-sync"
import { usePlatform } from "@/context/platform"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
@@ -12,6 +12,11 @@ import { decode64 } from "@/utils/base64"
import { EventSessionError } from "@opencode-ai/sdk/v2"
import { Persist, persisted } from "@/utils/persist"
import { playSoundById } from "@/utils/sound"
import { useGlobal } from "./global"
import { ServerConnection, useServer } from "./server"
import { type DraftTab, useTabs } from "./tabs"
import { requireServerKey } from "@/utils/session-route"
import type { ServerScope } from "@/utils/server-scope"
type NotificationBase = {
directory?: string
@@ -107,267 +112,360 @@ function buildNotificationIndex(list: Notification[]) {
export const { use: useNotification, provider: NotificationProvider } = createSimpleContext({
name: "Notification",
gate: false,
init: (props: { directory?: Accessor<string | undefined>; sessionID?: Accessor<string | undefined> }) => {
const params = useParams()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
init: () => {
const params = useParams<{ serverKey?: string; dir?: string; id?: string }>()
const [search] = useSearchParams<{ draftId?: string }>()
const global = useGlobal()
const server = useServer()
const tabs = useTabs()
const platform = usePlatform()
const settings = useSettings()
const language = useLanguage()
const owner = getOwner()
const states = new Map<ServerScope, { dispose: () => void; state: NotificationState }>()
const empty: Notification[] = []
const currentDirectory = createMemo(() => {
return props.directory?.() ?? decode64(params.dir)
const activeServer = createMemo(() => {
if (params.serverKey) return requireServerKey(params.serverKey)
if (search.draftId) {
const draft = tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)
if (draft) return draft.server
}
return server.key
})
const activeDirectory = createMemo(() => decode64(params.dir))
const activeSession = createMemo(() => params.id)
const currentSession = createMemo(() => props.sessionID?.() ?? params.id)
const [store, setStore, _, ready] = persisted(
Persist.serverGlobal(serverSDK().scope, "notification", ["notification.v1"]),
createStore({
list: [] as Notification[],
}),
)
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
const meta = { pruned: false, disposed: false }
const updateUnseen = (scope: "session" | "project", key: string, unseen: Notification[]) => {
setIndex(scope, "unseen", key, unseen)
setIndex(scope, "unseenCount", key, unseen.length)
setIndex(
scope,
"unseenHasError",
key,
unseen.some((notification) => notification.type === "error"),
const ensure = (key: ServerConnection.Key) => {
const conn = global.servers.list().find((item) => ServerConnection.key(item) === key)
if (!conn) throw new Error(`Notification server not found: ${key}`)
const ctx = global.ensureServerCtx(conn)
const existing = states.get(ctx.sdk.scope)
if (existing) return existing.state
const root = createRoot(
(dispose) => ({
dispose,
state: createServerNotificationState({
sdk: ctx.sdk,
sync: ctx.sync,
active: () => server.scope(activeServer()) === ctx.sdk.scope,
directory: activeDirectory,
sessionID: activeSession,
platform,
settings,
language,
}),
}),
owner ?? undefined,
)
}
const appendToIndex = (notification: Notification) => {
if (notification.session) {
setIndex("session", "all", notification.session, (all = []) => [...all, notification])
if (!notification.viewed) {
setIndex("session", "unseen", notification.session, (unseen = []) => [...unseen, notification])
setIndex("session", "unseenCount", notification.session, (count = 0) => count + 1)
if (notification.type === "error") setIndex("session", "unseenHasError", notification.session, true)
}
}
if (notification.directory) {
setIndex("project", "all", notification.directory, (all = []) => [...all, notification])
if (!notification.viewed) {
setIndex("project", "unseen", notification.directory, (unseen = []) => [...unseen, notification])
setIndex("project", "unseenCount", notification.directory, (count = 0) => count + 1)
if (notification.type === "error") setIndex("project", "unseenHasError", notification.directory, true)
}
}
}
const removeFromIndex = (notification: Notification) => {
if (notification.session) {
setIndex("session", "all", notification.session, (all = []) => all.filter((n) => n !== notification))
if (!notification.viewed) {
const unseen = (index.session.unseen[notification.session] ?? empty).filter((n) => n !== notification)
updateUnseen("session", notification.session, unseen)
}
}
if (notification.directory) {
setIndex("project", "all", notification.directory, (all = []) => all.filter((n) => n !== notification))
if (!notification.viewed) {
const unseen = (index.project.unseen[notification.directory] ?? empty).filter((n) => n !== notification)
updateUnseen("project", notification.directory, unseen)
}
}
states.set(ctx.sdk.scope, root)
return root.state
}
createEffect(() => {
if (!ready()) return
if (meta.pruned) return
meta.pruned = true
const list = pruneNotifications(store.list)
batch(() => {
setStore("list", list)
setIndex(reconcile(buildNotificationIndex(list), { merge: false }))
global.servers.list().forEach((conn) => ensure(ServerConnection.key(conn)))
})
createEffect(() => {
const scopes = new Set(global.servers.list().map((conn) => server.scope(ServerConnection.key(conn))))
states.forEach((value, scope) => {
if (scopes.has(scope)) return
value.dispose()
states.delete(scope)
})
})
const append = (notification: Notification) => {
const list = pruneNotifications([...store.list, notification])
const keep = new Set(list)
const removed = store.list.filter((n) => !keep.has(n))
onCleanup(() => states.forEach((value) => value.dispose()))
batch(() => {
if (keep.has(notification)) appendToIndex(notification)
removed.forEach((n) => removeFromIndex(n))
setStore("list", list)
})
}
const lookup = async (directory: string, sessionID?: string) => {
if (!sessionID) return undefined
const sync = serverSync().ensureDirSyncContext(directory)
const session = sync.session.get(sessionID)
if (session) return session
return sync.session
.sync(sessionID)
.then(() => sync.session.get(sessionID))
.catch(() => undefined)
}
const viewedInCurrentSession = (directory: string, sessionID?: string) => {
const activeDirectory = currentDirectory()
const activeSession = currentSession()
if (!activeDirectory) return false
if (!activeSession) return false
if (!sessionID) return false
if (directory !== activeDirectory) return false
return sessionID === activeSession
}
const handleSessionIdle = (directory: string, event: { properties: { sessionID?: string } }, time: number) => {
const sessionID = event.properties.sessionID
void lookup(directory, sessionID).then((session) => {
if (meta.disposed) return
if (!session) return
if (session.parentID) return
if (settings.sounds.agentEnabled()) {
void playSoundById(settings.sounds.agent())
}
append({
directory,
time,
viewed: viewedInCurrentSession(directory, sessionID),
type: "turn-complete",
session: sessionID,
})
const href = `/${base64Encode(directory)}/session/${sessionID}`
if (settings.notifications.agent()) {
void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, href)
}
})
}
const handleSessionError = (
directory: string,
event: { properties: { sessionID?: string; error?: EventSessionError["properties"]["error"] } },
time: number,
) => {
const sessionID = event.properties.sessionID
void lookup(directory, sessionID).then((session) => {
if (meta.disposed) return
if (session?.parentID) return
if (settings.sounds.errorsEnabled()) {
void playSoundById(settings.sounds.errors())
}
const error = "error" in event.properties ? event.properties.error : undefined
append({
directory,
time,
viewed: viewedInCurrentSession(directory, sessionID),
type: "error",
session: sessionID ?? "global",
error,
})
const description =
session?.title ??
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
if (settings.notifications.errors()) {
void platform.notify(language.t("notification.session.error.title"), description, href)
}
})
}
const unsub = serverSDK().event.listen((e) => {
const event = e.details
if (event.type !== "session.idle" && event.type !== "session.error") return
const directory = e.name
const time = Date.now()
if (event.type === "session.idle") {
handleSessionIdle(directory, event, time)
return
}
handleSessionError(directory, event, time)
})
onCleanup(() => {
meta.disposed = true
unsub()
})
const selected = () => ensure(activeServer())
return {
ready,
ready: () => selected().ready(),
ensureServerState: ensure,
session: {
all(session: string) {
return index.session.all[session] ?? empty
},
unseen(session: string) {
return index.session.unseen[session] ?? empty
},
unseenCount(session: string) {
return index.session.unseenCount[session] ?? 0
},
unseenHasError(session: string) {
return index.session.unseenHasError[session] ?? false
},
markViewed(session: string) {
const unseen = index.session.unseen[session] ?? empty
if (!unseen.length) return
const projects = [
...new Set(unseen.flatMap((notification) => (notification.directory ? [notification.directory] : []))),
]
batch(() => {
setStore("list", (n) => n.session === session && !n.viewed, "viewed", true)
updateUnseen("session", session, [])
projects.forEach((directory) => {
const next = (index.project.unseen[directory] ?? empty).filter(
(notification) => notification.session !== session,
)
updateUnseen("project", directory, next)
})
})
},
all: (session: string) => selected().session.all(session),
unseen: (session: string) => selected().session.unseen(session),
unseenCount: (session: string) => selected().session.unseenCount(session),
unseenHasError: (session: string) => selected().session.unseenHasError(session),
markViewed: (session: string) => selected().session.markViewed(session),
},
project: {
all(directory: string) {
return index.project.all[directory] ?? empty
},
unseen(directory: string) {
return index.project.unseen[directory] ?? empty
},
unseenCount(directory: string) {
return index.project.unseenCount[directory] ?? 0
},
unseenHasError(directory: string) {
return index.project.unseenHasError[directory] ?? false
},
markViewed(directory: string) {
const unseen = index.project.unseen[directory] ?? empty
if (!unseen.length) return
const sessions = [
...new Set(unseen.flatMap((notification) => (notification.session ? [notification.session] : []))),
]
batch(() => {
setStore("list", (n) => n.directory === directory && !n.viewed, "viewed", true)
updateUnseen("project", directory, [])
sessions.forEach((session) => {
const next = (index.session.unseen[session] ?? empty).filter(
(notification) => notification.directory !== directory,
)
updateUnseen("session", session, next)
})
})
},
all: (directory: string) => selected().project.all(directory),
unseen: (directory: string) => selected().project.unseen(directory),
unseenCount: (directory: string) => selected().project.unseenCount(directory),
unseenHasError: (directory: string) => selected().project.unseenHasError(directory),
markViewed: (directory: string) => selected().project.markViewed(directory),
},
}
},
})
type NotificationState = ReturnType<typeof createServerNotificationState>
function createServerNotificationState(input: {
sdk: ServerSDK
sync: ServerSync
active: Accessor<boolean>
directory: Accessor<string | undefined>
sessionID: Accessor<string | undefined>
platform: ReturnType<typeof usePlatform>
settings: ReturnType<typeof useSettings>
language: ReturnType<typeof useLanguage>
}) {
const serverSDK = () => input.sdk
const serverSync = () => input.sync
const platform = input.platform
const settings = input.settings
const language = input.language
const empty: Notification[] = []
const currentDirectory = input.directory
const currentSession = input.sessionID
const [store, setStore, _, ready] = persisted(
Persist.serverGlobal(serverSDK().scope, "notification", ["notification.v1"]),
createStore({
list: [] as Notification[],
}),
)
const [index, setIndex] = createStore<NotificationIndex>(buildNotificationIndex(store.list))
const meta = { pruned: false, disposed: false }
const updateUnseen = (scope: "session" | "project", key: string, unseen: Notification[]) => {
setIndex(scope, "unseen", key, unseen)
setIndex(scope, "unseenCount", key, unseen.length)
setIndex(
scope,
"unseenHasError",
key,
unseen.some((notification) => notification.type === "error"),
)
}
const appendToIndex = (notification: Notification) => {
if (notification.session) {
setIndex("session", "all", notification.session, (all = []) => [...all, notification])
if (!notification.viewed) {
setIndex("session", "unseen", notification.session, (unseen = []) => [...unseen, notification])
setIndex("session", "unseenCount", notification.session, (count = 0) => count + 1)
if (notification.type === "error") setIndex("session", "unseenHasError", notification.session, true)
}
}
if (notification.directory) {
setIndex("project", "all", notification.directory, (all = []) => [...all, notification])
if (!notification.viewed) {
setIndex("project", "unseen", notification.directory, (unseen = []) => [...unseen, notification])
setIndex("project", "unseenCount", notification.directory, (count = 0) => count + 1)
if (notification.type === "error") setIndex("project", "unseenHasError", notification.directory, true)
}
}
}
const removeFromIndex = (notification: Notification) => {
if (notification.session) {
setIndex("session", "all", notification.session, (all = []) => all.filter((n) => n !== notification))
if (!notification.viewed) {
const unseen = (index.session.unseen[notification.session] ?? empty).filter((n) => n !== notification)
updateUnseen("session", notification.session, unseen)
}
}
if (notification.directory) {
setIndex("project", "all", notification.directory, (all = []) => all.filter((n) => n !== notification))
if (!notification.viewed) {
const unseen = (index.project.unseen[notification.directory] ?? empty).filter((n) => n !== notification)
updateUnseen("project", notification.directory, unseen)
}
}
}
createEffect(() => {
if (!ready()) return
if (meta.pruned) return
meta.pruned = true
const list = pruneNotifications(store.list)
batch(() => {
setStore("list", list)
setIndex(reconcile(buildNotificationIndex(list), { merge: false }))
})
})
const append = (notification: Notification) => {
const list = pruneNotifications([...store.list, notification])
const keep = new Set(list)
const removed = store.list.filter((n) => !keep.has(n))
batch(() => {
if (keep.has(notification)) appendToIndex(notification)
removed.forEach((n) => removeFromIndex(n))
setStore("list", list)
})
}
const lookup = async (directory: string, sessionID?: string) => {
if (!sessionID) return undefined
const sync = serverSync().ensureDirSyncContext(directory)
const session = sync.session.get(sessionID)
if (session) return session
return sync.session
.sync(sessionID)
.then(() => sync.session.get(sessionID))
.catch(() => undefined)
}
const viewedInCurrentSession = (directory: string, sessionID?: string) => {
if (!input.active()) return false
const activeDirectory = currentDirectory()
const activeSession = currentSession()
if (!activeSession) return false
if (!sessionID) return false
if (activeDirectory && directory !== activeDirectory) return false
return sessionID === activeSession
}
const handleSessionIdle = (directory: string, event: { properties: { sessionID?: string } }, time: number) => {
const sessionID = event.properties.sessionID
void lookup(directory, sessionID).then((session) => {
if (meta.disposed) return
if (!session) return
if (session.parentID) return
if (settings.sounds.agentEnabled()) {
void playSoundById(settings.sounds.agent())
}
append({
directory,
time,
viewed: viewedInCurrentSession(directory, sessionID),
type: "turn-complete",
session: sessionID,
})
const href = `/${base64Encode(directory)}/session/${sessionID}`
if (settings.notifications.agent()) {
void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, href)
}
})
}
const handleSessionError = (
directory: string,
event: { properties: { sessionID?: string; error?: EventSessionError["properties"]["error"] } },
time: number,
) => {
const sessionID = event.properties.sessionID
void lookup(directory, sessionID).then((session) => {
if (meta.disposed) return
if (session?.parentID) return
if (settings.sounds.errorsEnabled()) {
void playSoundById(settings.sounds.errors())
}
const error = "error" in event.properties ? event.properties.error : undefined
append({
directory,
time,
viewed: viewedInCurrentSession(directory, sessionID),
type: "error",
session: sessionID ?? "global",
error,
})
const description =
session?.title ??
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
if (settings.notifications.errors()) {
void platform.notify(language.t("notification.session.error.title"), description, href)
}
})
}
const unsub = serverSDK().event.listen((e) => {
const event = e.details
if (event.type !== "session.idle" && event.type !== "session.error") return
const directory = e.name
const time = Date.now()
if (event.type === "session.idle") {
handleSessionIdle(directory, event, time)
return
}
handleSessionError(directory, event, time)
})
onCleanup(() => {
meta.disposed = true
unsub()
})
return {
ready,
session: {
all(session: string) {
return index.session.all[session] ?? empty
},
unseen(session: string) {
return index.session.unseen[session] ?? empty
},
unseenCount(session: string) {
return index.session.unseenCount[session] ?? 0
},
unseenHasError(session: string) {
return index.session.unseenHasError[session] ?? false
},
markViewed(session: string) {
const unseen = index.session.unseen[session] ?? empty
if (!unseen.length) return
const projects = [
...new Set(unseen.flatMap((notification) => (notification.directory ? [notification.directory] : []))),
]
batch(() => {
setStore("list", (n) => n.session === session && !n.viewed, "viewed", true)
updateUnseen("session", session, [])
projects.forEach((directory) => {
const next = (index.project.unseen[directory] ?? empty).filter(
(notification) => notification.session !== session,
)
updateUnseen("project", directory, next)
})
})
},
},
project: {
all(directory: string) {
return index.project.all[directory] ?? empty
},
unseen(directory: string) {
return index.project.unseen[directory] ?? empty
},
unseenCount(directory: string) {
return index.project.unseenCount[directory] ?? 0
},
unseenHasError(directory: string) {
return index.project.unseenHasError[directory] ?? false
},
markViewed(directory: string) {
const unseen = index.project.unseen[directory] ?? empty
if (!unseen.length) return
const sessions = [
...new Set(unseen.flatMap((notification) => (notification.session ? [notification.session] : []))),
]
batch(() => {
setStore("list", (n) => n.directory === directory && !n.viewed, "viewed", true)
updateUnseen("project", directory, [])
sessions.forEach((session) => {
const next = (index.session.unseen[session] ?? empty).filter(
(notification) => notification.directory !== directory,
)
updateUnseen("session", session, next)
})
})
},
},
}
}
+1 -9
View File
@@ -1,6 +1,7 @@
@import "@opencode-ai/ui/styles/tailwind";
@import "@opencode-ai/session-ui/styles";
@import "@opencode-ai/ui/v2/styles/tailwind.css";
@import "tw-animate-css";
@font-face {
font-family: "JetBrainsMono Nerd Font Mono";
@@ -131,13 +132,4 @@
transform: rotate(360deg);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
+24 -21
View File
@@ -68,6 +68,7 @@ import { archiveHomeSession } from "./home-session-archive"
import { showToast } from "@/utils/toast"
const HOME_SESSION_LIMIT = 64
const SHOW_HOME_SESSION_ARCHIVE = false
const HOME_ROW_LAYOUT =
"flex min-w-0 w-full shrink-0 cursor-default items-center rounded-[6px] bg-transparent text-left transition-[background-color,color,box-shadow] duration-[120ms] ease-in-out focus-visible:outline-none"
const HOME_ROW_BASE = `${HOME_ROW_LAYOUT} border-0`
@@ -341,15 +342,15 @@ export function NewHome() {
}
function unseenCount(conn: ServerConnection.Any, project: LocalProject) {
if (ServerConnection.key(conn) !== server.key) return 0
return directories(project).reduce((total, directory) => total + notification.project.unseenCount(directory), 0)
const state = notification.ensureServerState(ServerConnection.key(conn))
return directories(project).reduce((total, directory) => total + state.project.unseenCount(directory), 0)
}
function clearNotifications(conn: ServerConnection.Any, project: LocalProject) {
if (ServerConnection.key(conn) !== server.key) return
const state = notification.ensureServerState(ServerConnection.key(conn))
directories(project)
.filter((directory) => notification.project.unseenCount(directory) > 0)
.forEach((directory) => notification.project.markViewed(directory))
.filter((directory) => state.project.unseenCount(directory) > 0)
.forEach((directory) => state.project.markViewed(directory))
}
function openSession(session: Session) {
@@ -1189,22 +1190,24 @@ function HomeSessionRow(props: {
</span>
</Show>
</button>
<div class="hover-reveal absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-1 group-hover/session:opacity-100 focus-within:opacity-100">
<TooltipV2 class="flex shrink-0 items-center" placement="bottom" value={language.t("common.archive")}>
<IconButtonV2
data-action="home-session-archive"
variant="ghost-muted"
size="large"
icon={<IconV2 name="archive" />}
aria-label={language.t("common.archive")}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
void props.archiveSession(props.record.session)
}}
/>
</TooltipV2>
</div>
<Show when={SHOW_HOME_SESSION_ARCHIVE}>
<div class="hover-reveal absolute right-1.5 top-1/2 flex -translate-y-1/2 items-center gap-1 group-hover/session:opacity-100 focus-within:opacity-100">
<TooltipV2 class="flex shrink-0 items-center" placement="bottom" value={language.t("common.archive")}>
<IconButtonV2
data-action="home-session-archive"
variant="ghost-muted"
size="large"
icon={<IconV2 name="archive" />}
aria-label={language.t("common.archive")}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
void props.archiveSession(props.record.session)
}}
/>
</TooltipV2>
</div>
</Show>
</div>
)
}
+1 -9
View File
@@ -1,26 +1,18 @@
import { createEffect, Suspense, type ParentProps } from "solid-js"
import { useNavigate, useParams } from "@solidjs/router"
import { useNavigate } from "@solidjs/router"
import { DebugBar } from "@/components/debug-bar"
import { HelpButton } from "@/components/help-button"
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { useNotification } from "@/context/notification"
import { usePlatform } from "@/context/platform"
import { setNavigate } from "@/utils/notification-click"
import { setV2Toast, ToastRegion } from "@/utils/toast"
export default function NewLayout(props: ParentProps) {
const platform = usePlatform()
const notification = useNotification()
const navigate = useNavigate()
const params = useParams<{ id?: string }>()
setNavigate(navigate)
createEffect(() => setV2Toast(true))
createEffect(() => {
if (!notification.ready() || !params.id) return
if (notification.session.unseenCount(params.id) === 0) return
notification.session.markViewed(params.id)
})
const update: TitlebarUpdate = {
version: () => {
+15 -6
View File
@@ -1,20 +1,29 @@
import { NodeFileSystem } from "@effect/platform-node"
import { compile, emitEffectImported, emitPromise, write } from "@opencode-ai/httpapi-codegen"
import { Api } from "@opencode-ai/server/api"
import { ClientApi } from "../src/contract"
import { Effect } from "effect"
import { HttpApi } from "effect/unstable/httpapi"
import { fileURLToPath } from "url"
const contract = compile(HttpApi.make("opencode-client").add(Api.groups["server.session"]), {
groupNames: { "server.session": "sessions" },
const contract = compile(ClientApi, {
groupNames: { "server.session": "sessions", "server.event": "events" },
})
await Effect.runPromise(
Effect.all(
[
write(emitPromise(contract), fileURLToPath(new URL("../src/generated", import.meta.url))),
write(
emitEffectImported(contract, { module: "../contract", group: "SessionGroup" }),
emitPromise(contract, {
outputTypes: {
"events.subscribe": {
name: "OpenCodeEventEncoded",
import: 'import type { OpenCodeEventEncoded } from "@opencode-ai/protocol/groups/event"',
},
},
}),
fileURLToPath(new URL("../src/generated", import.meta.url)),
),
write(
emitEffectImported(contract, { module: "../contract", api: "ClientApi" }),
fileURLToPath(new URL("../src/generated-effect", import.meta.url)),
),
],
+3 -1
View File
@@ -1,6 +1,6 @@
import { makeDefaultApi } from "@opencode-ai/protocol/api"
import { InvalidRequestError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import { HttpApi, HttpApiMiddleware } from "effect/unstable/httpapi"
class LocationMiddleware extends HttpApiMiddleware.Service<LocationMiddleware>()(
"@opencode-ai/client/LocationMiddleware",
@@ -17,3 +17,5 @@ const Api = makeDefaultApi({
})
export const SessionGroup = Api.groups["server.session"]
export const EventGroup = Api.groups["server.event"]
export const ClientApi = HttpApi.make("opencode-client").add(SessionGroup).add(EventGroup)
+1
View File
@@ -10,3 +10,4 @@ export { Session } from "@opencode-ai/schema/session"
export { SessionInput } from "@opencode-ai/schema/session-input"
export { SessionMessage } from "@opencode-ai/schema/session-message"
export { Prompt } from "@opencode-ai/schema/prompt"
export type { OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
+43 -19
View File
@@ -2,13 +2,11 @@
import { Effect, Stream, Schema } from "effect"
import { Sse } from "effect/unstable/encoding"
import { HttpClientError } from "effect/unstable/http"
import { HttpApi, HttpApiClient } from "effect/unstable/httpapi"
import { SessionGroup } from "../contract"
import { HttpApiClient } from "effect/unstable/httpapi"
import { ClientApi } from "../contract"
import { ClientError } from "./client-error"
const Api = HttpApi.make("generated").add(SessionGroup)
type RawClient = HttpApiClient.ForApi<typeof Api>
type RawClient = HttpApiClient.ForApi<typeof ClientApi>
const mapClientError = <E>(error: E) =>
HttpClientError.isHttpClientError(error) || Schema.isSchemaError(error) || Sse.Retry.is(error)
@@ -149,12 +147,24 @@ const Endpoint0_12 = (raw: RawClient["server.session"]) => (input: Endpoint0_12I
Effect.map((value) => value.data),
)
type Endpoint0_13Request = Parameters<RawClient["server.session"]["session.events"]>[0]
type Endpoint0_13Request = Parameters<RawClient["server.session"]["session.history"]>[0]
type Endpoint0_13Input = {
readonly sessionID: Endpoint0_13Request["params"]["sessionID"]
readonly limit?: Endpoint0_13Request["query"]["limit"]
readonly after?: Endpoint0_13Request["query"]["after"]
}
const Endpoint0_13 = (raw: RawClient["server.session"]) => (input: Endpoint0_13Input) =>
raw["session.history"]({
params: { sessionID: input.sessionID },
query: { limit: input.limit, after: input.after },
}).pipe(Effect.mapError(mapClientError))
type Endpoint0_14Request = Parameters<RawClient["server.session"]["session.events"]>[0]
type Endpoint0_14Input = {
readonly sessionID: Endpoint0_14Request["params"]["sessionID"]
readonly after?: Endpoint0_14Request["query"]["after"]
}
const Endpoint0_14 = (raw: RawClient["server.session"]) => (input: Endpoint0_14Input) =>
Stream.unwrap(
raw["session.events"]({ params: { sessionID: input.sessionID }, query: { after: input.after } }).pipe(
Effect.mapError(mapClientError),
@@ -162,17 +172,17 @@ const Endpoint0_13 = (raw: RawClient["server.session"]) => (input: Endpoint0_13I
),
)
type Endpoint0_14Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint0_14Input = { readonly sessionID: Endpoint0_14Request["params"]["sessionID"] }
const Endpoint0_14 = (raw: RawClient["server.session"]) => (input: Endpoint0_14Input) =>
type Endpoint0_15Request = Parameters<RawClient["server.session"]["session.interrupt"]>[0]
type Endpoint0_15Input = { readonly sessionID: Endpoint0_15Request["params"]["sessionID"] }
const Endpoint0_15 = (raw: RawClient["server.session"]) => (input: Endpoint0_15Input) =>
raw["session.interrupt"]({ params: { sessionID: input.sessionID } }).pipe(Effect.mapError(mapClientError))
type Endpoint0_15Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint0_15Input = {
readonly sessionID: Endpoint0_15Request["params"]["sessionID"]
readonly messageID: Endpoint0_15Request["params"]["messageID"]
type Endpoint0_16Request = Parameters<RawClient["server.session"]["session.message"]>[0]
type Endpoint0_16Input = {
readonly sessionID: Endpoint0_16Request["params"]["sessionID"]
readonly messageID: Endpoint0_16Request["params"]["messageID"]
}
const Endpoint0_15 = (raw: RawClient["server.session"]) => (input: Endpoint0_15Input) =>
const Endpoint0_16 = (raw: RawClient["server.session"]) => (input: Endpoint0_16Input) =>
raw["session.message"]({ params: { sessionID: input.sessionID, messageID: input.messageID } }).pipe(
Effect.mapError(mapClientError),
Effect.map((value) => value.data),
@@ -192,12 +202,26 @@ const adaptGroup0 = (raw: RawClient["server.session"]) => ({
clear: Endpoint0_10(raw),
commit: Endpoint0_11(raw),
context: Endpoint0_12(raw),
events: Endpoint0_13(raw),
interrupt: Endpoint0_14(raw),
message: Endpoint0_15(raw),
history: Endpoint0_13(raw),
events: Endpoint0_14(raw),
interrupt: Endpoint0_15(raw),
message: Endpoint0_16(raw),
})
const adaptClient = (raw: RawClient) => ({ sessions: adaptGroup0(raw["server.session"]) })
const Endpoint1_0 = (raw: RawClient["server.event"]) => () =>
Stream.unwrap(
raw["event.subscribe"]({}).pipe(
Effect.mapError(mapClientError),
Effect.map((stream) => stream.pipe(Stream.mapError(mapClientError))),
),
)
const adaptGroup1 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint1_0(raw) })
const adaptClient = (raw: RawClient) => ({
sessions: adaptGroup0(raw["server.session"]),
events: adaptGroup1(raw["server.event"]),
})
export const make = (options?: { readonly baseUrl?: URL | string }) =>
HttpApiClient.make(Api, options).pipe(Effect.map(adaptClient))
HttpApiClient.make(ClientApi, options).pipe(Effect.map(adaptClient))
+22
View File
@@ -24,12 +24,15 @@ import type {
SessionsCommitOutput,
SessionsContextInput,
SessionsContextOutput,
SessionsHistoryInput,
SessionsHistoryOutput,
SessionsEventsInput,
SessionsEventsOutput,
SessionsInterruptInput,
SessionsInterruptOutput,
SessionsMessageInput,
SessionsMessageOutput,
EventsSubscribeOutput,
} from "./types"
import { ClientError } from "./client-error"
@@ -324,6 +327,18 @@ export function make(options: ClientOptions) {
},
requestOptions,
).then((value) => value.data),
history: (input: SessionsHistoryInput, requestOptions?: RequestOptions) =>
request<SessionsHistoryOutput>(
{
method: "GET",
path: `/api/session/${encodeURIComponent(input.sessionID)}/history`,
query: { limit: input.limit, after: input.after },
successStatus: 200,
declaredStatuses: [404, 400, 401],
empty: false,
},
requestOptions,
),
events: (input: SessionsEventsInput, requestOptions?: RequestOptions): AsyncIterable<SessionsEventsOutput> =>
sse<SessionsEventsOutput>(
{
@@ -359,6 +374,13 @@ export function make(options: ClientOptions) {
requestOptions,
).then((value) => value.data),
},
events: {
subscribe: (requestOptions?: RequestOptions): AsyncIterable<EventsSubscribeOutput> =>
sse<EventsSubscribeOutput>(
{ method: "GET", path: `/api/event`, successStatus: 200, declaredStatuses: [401, 400], empty: false },
requestOptions,
),
},
}
}
+473 -9
View File
@@ -1,3 +1,5 @@
import type { OpenCodeEventEncoded } from "@opencode-ai/protocol/groups/event"
export type JsonValue =
| null
| boolean
@@ -67,7 +69,7 @@ export const isUnknownError = (value: unknown): value is UnknownError =>
export type SessionsListInput = {
readonly workspace?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -77,7 +79,7 @@ export type SessionsListInput = {
}["workspace"]
readonly limit?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -87,7 +89,7 @@ export type SessionsListInput = {
}["limit"]
readonly order?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -97,7 +99,7 @@ export type SessionsListInput = {
}["order"]
readonly search?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -107,7 +109,7 @@ export type SessionsListInput = {
}["search"]
readonly directory?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -117,7 +119,7 @@ export type SessionsListInput = {
}["directory"]
readonly project?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -127,7 +129,7 @@ export type SessionsListInput = {
}["project"]
readonly subpath?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -137,7 +139,7 @@ export type SessionsListInput = {
}["subpath"]
readonly cursor?: {
readonly workspace?: string | undefined
readonly limit?: string | undefined
readonly limit?: number | undefined
readonly order?: "asc" | "desc" | undefined
readonly search?: string | undefined
readonly directory?: string | undefined
@@ -591,9 +593,469 @@ export type SessionsContextOutput = {
>
}["data"]
export type SessionsHistoryInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly limit?: { readonly limit?: number | undefined; readonly after?: number | undefined }["limit"]
readonly after?: { readonly limit?: number | undefined; readonly after?: number | undefined }["after"]
}
export type SessionsHistoryOutput = {
readonly data: ReadonlyArray<
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.agent.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly agent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.model.switched"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.moved"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly location: { readonly directory: string; readonly workspaceID?: string }
readonly subdirectory?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.prompted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.prompt.admitted"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly prompt: {
readonly text: string
readonly files?: ReadonlyArray<{
readonly uri: string
readonly mime: string
readonly name?: string
readonly description?: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
readonly agents?: ReadonlyArray<{
readonly name: string
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
}>
}
readonly delivery: "steer" | "queue"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.context.updated"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.synthetic"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.shell.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly callID: string
readonly command: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.shell.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly callID: string
readonly output: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly agent: string
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
readonly snapshot?: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly finish: string
readonly cost: number
readonly tokens: {
readonly input: number
readonly output: number
readonly reasoning: number
readonly cache: { readonly read: number; readonly write: number }
}
readonly snapshot?: string
readonly files?: ReadonlyArray<string>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.step.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly error: { readonly type: "unknown"; readonly message: string }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.text.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.text.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly textID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.input.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly name: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.input.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly text: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.called"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly tool: string
readonly input: { readonly [x: string]: JsonValue }
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.progress"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.success"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly structured: { readonly [x: string]: JsonValue }
readonly content: ReadonlyArray<
| { readonly type: "text"; readonly text: string }
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
>
readonly outputPaths?: ReadonlyArray<string>
readonly result?: JsonValue
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.tool.failed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly callID: string
readonly error: { readonly type: "unknown"; readonly message: string }
readonly result?: JsonValue
readonly provider: {
readonly executed: boolean
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.reasoning.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.reasoning.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly assistantMessageID: string
readonly reasoningID: string
readonly text: string
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.retried"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly attempt: number
readonly error: {
readonly message: string
readonly statusCode?: number
readonly isRetryable: boolean
readonly responseHeaders?: { readonly [x: string]: string }
readonly responseBody?: string
readonly metadata?: { readonly [x: string]: string }
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.compaction.started"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.compaction.ended"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly messageID: string
readonly reason: "auto" | "manual"
readonly text: string
readonly recent: string
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.staged"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: {
readonly timestamp: number
readonly sessionID: string
readonly revert: {
readonly messageID: string
readonly partID?: string
readonly snapshot?: string
readonly diff?: string
readonly files?: ReadonlyArray<{
readonly path: string
readonly status: "added" | "modified" | "deleted"
readonly additions: number
readonly deletions: number
readonly patch: string
}>
}
}
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.cleared"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string }
}
| {
readonly id: string
readonly metadata?: { readonly [x: string]: JsonValue }
readonly type: "session.next.revert.committed"
readonly durable?: { readonly aggregateID: string; readonly seq: number; readonly version: number }
readonly location?: { readonly directory: string; readonly workspaceID?: string }
readonly data: { readonly timestamp: number; readonly sessionID: string; readonly messageID: string }
}
>
readonly hasMore: boolean
}
export type SessionsEventsInput = {
readonly sessionID: { readonly sessionID: string }["sessionID"]
readonly after?: { readonly after?: string | undefined }["after"]
readonly after?: { readonly after?: number | undefined }["after"]
}
export type SessionsEventsOutput =
@@ -1206,3 +1668,5 @@ export type SessionsMessageOutput = {
readonly time: { readonly created: number }
}
}["data"]
export type EventsSubscribeOutput = OpenCodeEventEncoded
+1
View File
@@ -1 +1,2 @@
export * from "./generated/index"
export type { EventsSubscribeOutput as OpenCodeEvent } from "./generated/types"
@@ -20,7 +20,7 @@ import { Workspace } from "@opencode-ai/schema/workspace"
import { Api } from "@opencode-ai/server/api"
import { compile, emitPromise } from "@opencode-ai/httpapi-codegen"
import { HttpApi } from "effect/unstable/httpapi"
import { SessionGroup } from "../src/contract"
import { EventGroup, SessionGroup } from "../src/contract"
test("Core and Server reuse the authoritative Schema and Protocol values", () => {
expect(AgentV2.ID).toBe(Agent.ID)
@@ -32,6 +32,7 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
expect(CorePrompt).toBe(Prompt)
expect(Api.groups["server.session"].identifier).toBe("server.session")
expect(SessionGroup.identifier).toBe(Api.groups["server.session"].identifier)
expect(EventGroup.identifier).toBe(Api.groups["server.event"].identifier)
expect(Session.ID.create()).toStartWith("ses_")
expect(Project.ID.global).toBe("global")
expect(Provider.ID.anthropic).toBe("anthropic")
+100 -1
View File
@@ -15,7 +15,53 @@ test("sessions.get returns the decoded Effect projection", async () => {
expect(DateTime.toEpochMillis(result.time.created)).toBe(1_717_171_717_000)
})
test("events.subscribe exposes and decodes the native Effect event stream", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(
`data: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n` +
`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`,
{ headers: { "content-type": "text/event-stream" } },
),
),
),
)
const events = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.events.subscribe().pipe(Stream.runCollect)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(Array.from(events).map((event) => event.type)).toEqual(["server.connected", "session.next.model.switched"])
const durable = events[1]
if (durable?.type !== "session.next.model.switched") throw new Error("Expected model event")
expect(DateTime.toEpochMillis(durable.data.timestamp)).toBe(1_717_171_717_000)
expect(durable.durable).toEqual({ aggregateID: "ses_test", seq: 1, version: 1 })
})
test("events.subscribe terminates on Effect protocol decode failures", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(`data: {"type":"server.connected"}\n\n`, {
headers: { "content-type": "text/event-stream" },
}),
),
),
)
const error = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.events.subscribe().pipe(Stream.runCollect, Effect.flip)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(error._tag).toBe("ClientError")
})
test("session methods retain decoded Effect inputs and outputs", async () => {
const historyQueries: Array<Record<string, string>> = []
let historyPage = 0
const httpClient = HttpClient.make((request) => {
const url = request.url
if (url.includes("/event")) {
@@ -28,6 +74,18 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
),
)
}
if (url.includes("/history")) {
historyPage++
historyQueries.push(Object.fromEntries(request.urlParams.params))
return Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json(
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
),
),
)
}
if (url.includes("/prompt")) {
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(admission)))
}
@@ -72,6 +130,18 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
yield* client.sessions.compact({ sessionID: Session.ID.make("ses_test") })
yield* client.sessions.wait({ sessionID: Session.ID.make("ses_test") })
const context = yield* client.sessions.context({ sessionID: Session.ID.make("ses_test") })
const history = yield* client.sessions.history({
sessionID: Session.ID.make("ses_test"),
after: 0,
limit: 1,
})
const historyNext = history.hasMore
? yield* client.sessions.history({
sessionID: Session.ID.make("ses_test"),
after: history.data.at(-1)?.durable?.seq,
limit: 2,
})
: undefined
const events = yield* client.sessions
.events({ sessionID: Session.ID.make("ses_test"), after: 0 })
.pipe(Stream.runCollect)
@@ -80,7 +150,7 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
sessionID: Session.ID.make("ses_test"),
messageID: SessionMessage.ID.make("msg_model"),
})
return { page, active, created, admitted, context, events, message }
return { page, active, created, admitted, context, history, historyNext, events, message }
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(DateTime.toEpochMillis(result.page.data[0].time.created)).toBe(1_717_171_717_000)
@@ -92,10 +162,39 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
expect(Object.getPrototypeOf(result.admitted.prompt)).toBe(Object.prototype)
expect(DateTime.toEpochMillis(result.admitted.timeCreated)).toBe(1_717_171_717_000)
expect(result.context).toEqual([])
expect(DateTime.toEpochMillis(result.history.data[0].data.timestamp)).toBe(1_717_171_717_000)
expect(result.history).toEqual(expect.objectContaining({ hasMore: true }))
expect(result.historyNext).toEqual({ data: [], hasMore: false })
expect(historyQueries[0]).toEqual({ limit: "1", after: "0" })
expect(historyQueries[1]).toEqual({ limit: "2", after: "1" })
expect(DateTime.toEpochMillis(result.events[0].data.timestamp)).toBe(1_717_171_717_000)
expect(result.message).toEqual(expect.objectContaining({ id: "msg_model", type: "model-switched" }))
})
test("sessions.history retains the typed SessionNotFoundError", async () => {
const httpClient = HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
request,
Response.json(
{ _tag: "SessionNotFoundError", sessionID: "ses_missing", message: "Session not found" },
{ status: 404 },
),
),
),
)
const error = await Effect.gen(function* () {
const client = yield* OpenCode.make({ baseUrl: "http://localhost:3000" })
return yield* client.sessions
.history({
sessionID: Session.ID.make("ses_missing"),
})
.pipe(Effect.flip)
}).pipe(Effect.provideService(HttpClient.HttpClient, httpClient), Effect.runPromise)
expect(error._tag).toBe("SessionNotFoundError")
})
const session = {
data: {
id: "ses_test",
+66 -3
View File
@@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { isUnauthorizedError, OpenCode } from "../src"
import { isSessionNotFoundError, isUnauthorizedError, OpenCode } from "../src"
test("sessions.get returns the wire projection", async () => {
const client = OpenCode.make({
@@ -17,8 +17,38 @@ test("sessions.get returns the wire projection", async () => {
expect(result.time.created).toBe(1_717_171_717_000)
})
test("events.subscribe exposes the Promise event stream wire projection", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () =>
new Response(
`: heartbeat\n\ndata: ${JSON.stringify({ id: "evt_connected", type: "server.connected", data: {} })}\n\n` +
`data: ${JSON.stringify(modelSwitchedEvent)}\n\n`,
{ headers: { "content-type": "text/event-stream" } },
),
})
const events = []
for await (const event of client.events.subscribe()) events.push(event)
expect(events).toEqual([{ id: "evt_connected", type: "server.connected", data: {} }, modelSwitchedEvent])
expect(events[1]?.type === "session.next.model.switched" && events[1].data.timestamp).toBe(1_717_171_717_000)
})
test("events.subscribe terminates on malformed Promise SSE data", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () => new Response("data: {not-json}\n\n", { headers: { "content-type": "text/event-stream" } }),
})
await expect(client.events.subscribe()[Symbol.asyncIterator]().next()).rejects.toMatchObject({
name: "ClientError",
reason: "MalformedResponse",
})
})
test("session methods use the public HTTP contract", async () => {
const requests: Array<{ url: string; init?: RequestInit }> = []
let historyPage = 0
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async (input, init) => {
@@ -29,6 +59,12 @@ test("session methods use the public HTTP contract", async () => {
headers: { "content-type": "text/event-stream" },
})
}
if (url.includes("/history")) {
historyPage++
return Response.json(
historyPage === 1 ? { data: [modelSwitchedEvent], hasMore: true } : { data: [], hasMore: false },
)
}
if (url.includes("/prompt")) return Response.json(admission)
if (url.includes("/context")) return Response.json({ data: [] })
if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
@@ -39,7 +75,7 @@ test("session methods use the public HTTP contract", async () => {
},
})
const page = await client.sessions.list({ limit: "10", order: "desc" })
const page = await client.sessions.list({ limit: 10, order: "desc" })
const active = await client.sessions.active()
const created = await client.sessions.create({ location: { directory: "/tmp/project" } })
await client.sessions.switchAgent({ sessionID: "ses_test", agent: "build" })
@@ -55,8 +91,13 @@ test("session methods use the public HTTP contract", async () => {
await client.sessions.compact({ sessionID: "ses_test" })
await client.sessions.wait({ sessionID: "ses_test" })
const context = await client.sessions.context({ sessionID: "ses_test" })
const history = await client.sessions.history({ sessionID: "ses_test", after: 0, limit: 1 })
const historyAfter = history.data.at(-1)?.durable?.seq
const historyNext = history.hasMore
? await client.sessions.history({ sessionID: "ses_test", after: historyAfter, limit: 2 })
: undefined
const events = []
for await (const event of client.sessions.events({ sessionID: "ses_test", after: "0" })) events.push(event)
for await (const event of client.sessions.events({ sessionID: "ses_test", after: 0 })) events.push(event)
await client.sessions.interrupt({ sessionID: "ses_test" })
const message = await client.sessions.message({ sessionID: "ses_test", messageID: "msg_model" })
@@ -65,6 +106,8 @@ test("session methods use the public HTTP contract", async () => {
expect(created.id).toBe("ses_test")
expect(admitted.id).toBe("msg_test")
expect(context).toEqual([])
expect(history).toEqual({ data: [modelSwitchedEvent], hasMore: true })
expect(historyNext).toEqual({ data: [], hasMore: false })
expect(events).toEqual([modelSwitchedEvent])
expect(message).toEqual(modelSwitchedMessage)
expect(requests.map((request) => [request.init?.method, request.url])).toEqual([
@@ -77,6 +120,8 @@ test("session methods use the public HTTP contract", async () => {
["POST", "http://localhost:3000/api/session/ses_test/compact"],
["POST", "http://localhost:3000/api/session/ses_test/wait"],
["GET", "http://localhost:3000/api/session/ses_test/context"],
["GET", "http://localhost:3000/api/session/ses_test/history?limit=1&after=0"],
["GET", "http://localhost:3000/api/session/ses_test/history?limit=2&after=1"],
["GET", "http://localhost:3000/api/session/ses_test/event?after=0"],
["POST", "http://localhost:3000/api/session/ses_test/interrupt"],
["GET", "http://localhost:3000/api/session/ses_test/message/msg_model"],
@@ -104,6 +149,24 @@ test("middleware errors remain declared client errors", async () => {
}
})
test("sessions.history decodes SessionNotFoundError", async () => {
const client = OpenCode.make({
baseUrl: "http://localhost:3000",
fetch: async () =>
Response.json(
{ _tag: "SessionNotFoundError", sessionID: "ses_missing", message: "Session not found" },
{ status: 404 },
),
})
try {
await client.sessions.history({ sessionID: "ses_missing" })
throw new Error("Expected request to fail")
} catch (error) {
expect(isSessionNotFoundError(error)).toBe(true)
}
})
const session = {
data: {
id: "ses_test",
@@ -215,6 +215,16 @@ export async function handler(
body: reqBody,
})
if (providerInfo.id.startsWith("console.")) {
const resEndpointId = res.headers.get("x-opencode-endpoint-id")
const resEndpointModelId = res.headers.get("x-opencode-upstream-model-id")
if (resEndpointId && resEndpointModelId)
logger.metric({
provider: resEndpointId,
"provider.model": resEndpointModelId,
})
}
if (res.status !== 200) {
logger.metric({
"llm.error.code": res.status,
+81 -15
View File
@@ -1,9 +1,9 @@
export * as EventV2 from "./event"
import { Cause, Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
import { Cause, Context, Effect, Layer, Option, PubSub, Queue, Schema, Stream } from "effect"
import { Event } from "@opencode-ai/schema/event"
import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
import { and, asc, eq, gt } from "drizzle-orm"
import { and, asc, eq, gt, inArray } from "drizzle-orm"
import { Database } from "./database/database"
import { EventSequenceTable, EventTable } from "./event/sql"
import { Location } from "./location"
@@ -47,6 +47,71 @@ export class InvalidDurableEventError extends Schema.TaggedErrorClass<InvalidDur
},
) {}
const decodeSerializedEvent = (event: SerializedEvent): Payload => {
const definition = Durable.get(event.type)
if (!definition?.durable) {
throw new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` })
}
return {
id: event.id,
type: definition.type,
durable: { aggregateID: event.aggregateID, seq: event.seq, version: definition.durable.version },
data: Schema.decodeUnknownSync(definition.data)(event.data),
}
}
export const readAggregate = Effect.fn("EventV2.readAggregate")(function* <A>(
db: Database.Interface["db"],
input: {
readonly aggregateID: string
readonly after?: number
readonly limit: number
readonly manifest: {
readonly definitions: ReadonlyMap<string, Definition>
readonly schema: Schema.Decoder<A, never>
}
},
) {
const after = input.after ?? -1
const rows = yield* db
.select()
.from(EventTable)
.where(
and(
eq(EventTable.aggregate_id, input.aggregateID),
gt(EventTable.seq, after),
inArray(EventTable.type, Array.from(input.manifest.definitions.keys())),
),
)
.orderBy(asc(EventTable.seq))
.limit(input.limit + 1)
.all()
.pipe(Effect.orDie)
const page = rows.slice(0, input.limit)
const decode = Schema.decodeUnknownSync(input.manifest.schema)
const events = page.map((event) =>
decode({
id: event.id,
type: input.manifest.definitions.get(event.type)?.type ?? event.type,
durable: {
aggregateID: event.aggregate_id,
seq: event.seq,
version: input.manifest.definitions.get(event.type)?.durable?.version,
},
data: event.data,
}),
)
return {
events,
hasMore: rows.length > input.limit,
}
})
export class SubscriberOverflowError extends Schema.TaggedErrorClass<SubscriberOverflowError>()(
"EventV2.SubscriberOverflow",
{ capacity: Schema.Int },
) {}
export const define = Event.define
export const versionedType = Event.versionedType
@@ -84,6 +149,20 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Event") {}
export const allBounded = (events: Interface, capacity: number) =>
Effect.gen(function* () {
const queue = yield* Queue.dropping<Payload, SubscriberOverflowError>(capacity)
const unsubscribe = yield* events.listen((event) =>
Queue.offer(queue, event).pipe(
Effect.flatMap((accepted) =>
accepted ? Effect.void : Queue.fail(queue, new SubscriberOverflowError({ capacity })).pipe(Effect.asVoid),
),
),
)
yield* Effect.addFinalizer(() => unsubscribe.pipe(Effect.andThen(Queue.shutdown(queue)), Effect.asVoid))
return Stream.fromQueue(queue)
})
export interface LayerOptions {
readonly beforeAggregateRead?: (aggregateID: string) => Effect.Effect<void>
}
@@ -459,19 +538,6 @@ export const layerWith = (options?: LayerOptions) =>
const streamAll = (): Stream.Stream<Payload> => Stream.fromPubSub(pubsub.all)
const decodeSerializedEvent = (event: SerializedEvent) => {
const definition = Durable.get(event.type)
if (!definition?.durable) {
throw new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` })
}
return {
id: event.id,
type: definition.type,
durable: { aggregateID: event.aggregateID, seq: event.seq, version: definition.durable.version },
data: Schema.decodeUnknownSync(definition.data)(event.data),
}
}
const readAfter = (aggregateID: string, after: number) =>
(options?.beforeAggregateRead?.(aggregateID) ?? Effect.void).pipe(
Effect.andThen(
+14
View File
@@ -34,6 +34,7 @@ import { Snapshot } from "./snapshot"
import { SessionRevert } from "./session/revert"
import { Revert } from "@opencode-ai/schema/revert"
import { FSUtil } from "./fs-util"
import { SessionDurable } from "@opencode-ai/schema/durable-event-manifest"
export const RevertState = Revert.State
export type RevertState = Revert.State
@@ -131,6 +132,11 @@ export interface Interface {
sessionID: SessionSchema.ID
after?: number
}) => Stream.Stream<SessionEvent.DurableEvent, NotFoundError>
readonly history: (input: {
sessionID: SessionSchema.ID
after?: number
limit: number
}) => Effect.Effect<{ events: ReadonlyArray<SessionEvent.DurableEvent>; hasMore: boolean }, NotFoundError>
readonly switchAgent: (input: { sessionID: SessionSchema.ID; agent: string }) => Effect.Effect<void, NotFoundError>
readonly switchModel: (input: {
sessionID: SessionSchema.ID
@@ -347,6 +353,14 @@ export const layer = Layer.unwrap(
.get(input.sessionID)
.pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))),
).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))),
history: Effect.fn("V2Session.history")(function* (input) {
yield* result.get(input.sessionID)
return yield* EventV2.readAggregate(db, {
...input,
aggregateID: input.sessionID,
manifest: SessionDurable,
})
}),
prompt: Effect.fn("V2Session.prompt")((input) =>
Effect.uninterruptible(
Effect.gen(function* () {
+64 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Stream } from "effect"
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Schema, Stream } from "effect"
import { EventV2 } from "@opencode-ai/core/event"
import { Event } from "@opencode-ai/schema/event"
import { Session } from "@opencode-ai/schema/session"
@@ -285,6 +285,69 @@ describe("EventV2", () => {
}),
)
it.effect("notifies global listeners only after a durable event is committed", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const { db } = yield* Database.Service
const aggregateID = EventV2.ID.create()
const observed = new Array<{ id: string; seq: number }>()
yield* events.listen((event) =>
event.type !== SyncMessage.type
? Effect.void
: db
.select({ id: EventTable.id, seq: EventTable.seq })
.from(EventTable)
.where(eq(EventTable.id, event.id))
.get()
.pipe(
Effect.orDie,
Effect.tap((row) =>
Effect.sync(() => {
if (row) observed.push(row)
}),
),
Effect.asVoid,
),
)
const event = yield* events.publish(SyncMessage, { id: aggregateID, text: "committed" })
if (!event.durable) throw new Error("Expected durable event metadata")
expect(observed).toEqual([{ id: event.id, seq: event.durable.seq }])
}),
)
it.effect("ends only an overflowing bounded subscriber without blocking other listeners", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const consuming = yield* Deferred.make<void>()
const release = yield* Deferred.make<void>()
const slowStream = yield* EventV2.allBounded(events, 1)
const fastStream = yield* EventV2.allBounded(events, 8)
const slow = yield* slowStream.pipe(
Stream.runForEach(() => Deferred.succeed(consuming, undefined).pipe(Effect.andThen(Deferred.await(release)))),
Effect.forkScoped,
)
const fast = yield* fastStream.pipe(Stream.take(4), Stream.runCollect, Effect.forkScoped)
yield* events.publish(Message, { text: "one" })
yield* Deferred.await(consuming)
yield* events.publish(Message, { text: "two" })
yield* events.publish(Message, { text: "overflow" })
const last = yield* events.publish(Message, { text: "still delivered" })
yield* Deferred.succeed(release, undefined)
const slowExit = yield* Fiber.await(slow)
expect(Exit.findErrorOption(slowExit).pipe(Option.getOrUndefined)).toBeInstanceOf(EventV2.SubscriberOverflowError)
expect(Array.from(yield* Fiber.join(fast))).toEqual([
expect.objectContaining({ data: { text: "one" } }),
expect.objectContaining({ data: { text: "two" } }),
expect.objectContaining({ data: { text: "overflow" } }),
last,
])
}),
)
it.effect("preserves observer interruption", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
+174
View File
@@ -0,0 +1,174 @@
import { describe, expect } from "bun:test"
import { Effect, Layer, Schema } from "effect"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2 } from "@opencode-ai/core/event"
import { Location } from "@opencode-ai/core/location"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { ProjectV2 } from "@opencode-ai/core/project"
import { ProjectTable } from "@opencode-ai/core/project/sql"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { SessionV2 } from "@opencode-ai/core/session"
import { SessionExecution } from "@opencode-ai/core/session/execution"
import { SessionProjector } from "@opencode-ai/core/session/projector"
import { SessionStore } from "@opencode-ai/core/session/store"
import { SessionTable } from "@opencode-ai/core/session/sql"
import { testEffect } from "./lib/effect"
const projects = Layer.succeed(
ProjectV2.Service,
ProjectV2.Service.of({
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
directories: () => Effect.succeed([]),
commit: () => Effect.void,
}),
)
const sessions = SessionV2.layer.pipe(
Layer.provide(LocationServiceMap.layer),
Layer.provide(EventV2.defaultLayer),
Layer.provide(Database.defaultLayer),
Layer.provide(SessionStore.defaultLayer),
Layer.provide(projects),
Layer.provide(SessionExecution.noopLayer),
)
const it = testEffect(
Layer.mergeAll(
Database.defaultLayer,
EventV2.defaultLayer,
projects,
SessionProjector.defaultLayer,
SessionStore.defaultLayer,
SessionExecution.noopLayer,
sessions,
),
)
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
const GapEvent = EventV2.define({
type: "test.session.history.gap",
durable: { aggregate: "sessionID", version: 1 },
schema: { sessionID: SessionV2.ID, value: Schema.String },
})
describe("SessionV2.history", () => {
it.effect("returns an exhausted page for a migrated Session with no event sequence", () =>
Effect.gen(function* () {
const db = (yield* Database.Service).db
const session = yield* SessionV2.Service
const sessionID = SessionV2.ID.make("ses_empty_history")
yield* db
.insert(ProjectTable)
.values({ id: ProjectV2.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
.onConflictDoNothing()
.run()
yield* db
.insert(SessionTable)
.values({
id: sessionID,
project_id: ProjectV2.ID.global,
slug: "empty-history",
directory: "/project",
title: "Empty history",
version: "test",
})
.run()
const first = yield* session.history({ sessionID, limit: 10 })
expect(first).toEqual({ events: [], hasMore: false })
}),
)
it.effect("treats after as an exclusive aggregate sequence", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const created = yield* session.create({ location })
yield* session.switchAgent({ sessionID: created.id, agent: "one" })
yield* session.switchAgent({ sessionID: created.id, agent: "two" })
const page = yield* session.history({ sessionID: created.id, after: 1, limit: 10 })
expect(page.events.map((event) => event.durable?.seq)).toEqual([2])
expect(page.hasMore).toBe(false)
}),
)
it.effect("paginates public events in aggregate order across filtered gaps without duplicates", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const events = yield* EventV2.Service
const created = yield* session.create({ location })
yield* session.switchAgent({ sessionID: created.id, agent: "one" })
yield* events.publish(GapEvent, { sessionID: created.id, value: "filtered" })
yield* session.switchAgent({ sessionID: created.id, agent: "two" })
yield* session.switchAgent({ sessionID: created.id, agent: "three" })
const first = yield* session.history({ sessionID: created.id, limit: 2 })
const after = first.events.at(-1)?.durable?.seq
const second = yield* session.history({
sessionID: created.id,
after,
limit: 2,
})
const sequence = [...first.events, ...second.events].map((event) => event.durable?.seq)
expect(first.hasMore).toBe(true)
expect(second.hasMore).toBe(false)
expect(sequence).toEqual([1, 3, 4])
expect(new Set(sequence).size).toBe(sequence.length)
}),
)
it.effect("includes events committed between pages", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const created = yield* session.create({ location })
yield* session.switchAgent({ sessionID: created.id, agent: "one" })
yield* session.switchAgent({ sessionID: created.id, agent: "two" })
const first = yield* session.history({ sessionID: created.id, limit: 1 })
yield* session.switchAgent({ sessionID: created.id, agent: "later" })
const second = yield* session.history({
sessionID: created.id,
after: first.events.at(-1)?.durable?.seq,
limit: 10,
})
expect(first.hasMore).toBe(true)
expect([...first.events, ...second.events].map((event) => event.durable?.seq)).toEqual([1, 2, 3])
expect(second.hasMore).toBe(false)
}),
)
it.effect("reports exhaustion for exact-limit and limit-plus-one pages", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const created = yield* session.create({ location })
yield* session.switchAgent({ sessionID: created.id, agent: "one" })
yield* session.switchAgent({ sessionID: created.id, agent: "two" })
const exact = yield* session.history({ sessionID: created.id, limit: 2 })
const oneMore = yield* session.history({ sessionID: created.id, limit: 1 })
const exhausted = yield* session.history({
sessionID: created.id,
after: oneMore.events.at(-1)?.durable?.seq,
limit: 1,
})
expect(exact.events).toHaveLength(2)
expect(exact.hasMore).toBe(false)
expect(oneMore.events).toHaveLength(1)
expect(oneMore.hasMore).toBe(true)
expect(exhausted.events).toHaveLength(1)
expect(exhausted.hasMore).toBe(false)
}),
)
it.effect("fails with NotFoundError for a missing Session", () =>
Effect.gen(function* () {
const session = yield* SessionV2.Service
const error = yield* session.history({ sessionID: SessionV2.ID.make("ses_missing"), limit: 10 }).pipe(Effect.flip)
expect(error._tag).toBe("Session.NotFoundError")
}),
)
})
+28 -17
View File
@@ -230,7 +230,12 @@ export function emitEffectImported(
}
}
export function emitPromise(contract: Contract): Output {
export function emitPromise(
contract: Contract,
options?: {
readonly outputTypes?: Readonly<Record<string, { readonly name: string; readonly import: string }>>
},
): Output {
const groups = contract.groups
for (const group of groups) {
for (const endpoint of group.endpoints) assertPromiseEndpoint(endpoint)
@@ -238,7 +243,7 @@ export function emitPromise(contract: Contract): Output {
return {
operations: operations(groups),
files: [
{ path: "types.ts", content: renderPromiseTypes(groups) },
{ path: "types.ts", content: renderPromiseTypes(groups, options?.outputTypes) },
{
path: "client-error.ts",
content: `export type ClientErrorReason = "Transport" | "UnexpectedStatus" | "UnsupportedContentType" | "MalformedResponse"\n\nexport class ClientError extends Error {\n override readonly name = "ClientError"\n constructor(readonly reason: ClientErrorReason, options?: ErrorOptions) {\n super(reason, options)\n }\n}\n`,
@@ -408,14 +413,17 @@ function renderImportedProjection(groups: ReadonlyArray<Group>, endpoints: Reado
return { imports: [...new Set(imports)], source }
}
function renderPromiseTypes(groups: ReadonlyArray<Group>) {
function renderPromiseTypes(
groups: ReadonlyArray<Group>,
outputTypes?: Readonly<Record<string, { readonly name: string; readonly import: string }>>,
) {
const types = new Map<SchemaAST.AST, string>()
const typeOf = (schema: Schema.Top) => {
const encoded = Schema.toEncoded(schema)
const cached = types.get(encoded.ast)
const typeOf = (schema: Schema.Top, decoded = false) => {
const projected = decoded ? Schema.toType(schema) : Schema.toEncoded(schema)
const cached = types.get(projected.ast)
if (cached !== undefined) return cached
const type = structuralType(encoded)
types.set(encoded.ast, type)
const type = structuralType(projected)
types.set(projected.ast, type)
return type
}
const errors = new Map(
@@ -449,17 +457,19 @@ function renderPromiseTypes(groups: ReadonlyArray<Group>) {
const schema = schemas[field.source]
if (schema === undefined)
throw new GenerationError({ reason: `Missing input schema: ${prefix}.${field.name}` })
return `readonly ${JSON.stringify(field.name)}${field.optional ? "?" : ""}: (${typeOf(schema)})[${JSON.stringify(field.name)}]`
return `readonly ${JSON.stringify(field.name)}${field.optional ? "?" : ""}: (${typeOf(schema, field.source === "query")})[${JSON.stringify(field.name)}]`
})
.join("; ")
const successSchema = endpoint.successes[0]
const success = typeOf(
isStreamSchema(successSchema) && successSchema._tag === "StreamSse"
? successSchema.sseMode === "data"
? streamEncodedDataSchema(successSchema)
: successSchema.events
: successSchema,
)
const success =
outputTypes?.[`${group.identifier}.${endpoint.operation.name}`]?.name ??
typeOf(
isStreamSchema(successSchema) && successSchema._tag === "StreamSse"
? successSchema.sseMode === "data"
? streamEncodedDataSchema(successSchema)
: successSchema.events
: successSchema,
)
return [
...(endpoint.operation.inputMode === "none" ? [] : [`export type ${prefix}Input = { ${input} }`]),
`export type ${prefix}Output = ${endpoint.unwrapData ? `(${success})["data"]` : success}`,
@@ -470,7 +480,8 @@ function renderPromiseTypes(groups: ReadonlyArray<Group>) {
const json = operations.includes("JsonValue")
? "export type JsonValue = null | boolean | number | string | ReadonlyArray<JsonValue> | { readonly [key: string]: JsonValue }"
: ""
return [json, ...errorTypes, operations].filter(Boolean).join("\n\n")
const imports = [...new Set(Object.values(outputTypes ?? {}).map((override) => override.import))]
return [...imports, json, ...errorTypes, operations].filter(Boolean).join("\n\n")
}
function renderPromiseClient(groups: ReadonlyArray<Group>) {
@@ -48,6 +48,24 @@ describe("HttpApiCodegen.generate", () => {
)
})
test("allows Promise outputs to use an authoritative imported wire type", () => {
const contract = compileContract(
api(HttpApiEndpoint.get("events", "/event", { success: HttpApiSchema.StreamSse({ data: Schema.Unknown }) })),
)
const output = emitPromise(contract, {
outputTypes: {
"session.events": {
name: "EventWire",
import: 'import type { EventWire } from "./event-wire"',
},
},
})
const types = output.files.find((file) => file.path === "types.ts")?.content
expect(types).toContain('import type { EventWire } from "./event-wire"')
expect(types).toContain("export type SessionEventsOutput = EventWire")
})
test("emits an Effect client against an imported authoritative API", () => {
const output = emitEffectImported(
compileContract(
+51 -3
View File
@@ -22,6 +22,8 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
import { EventV2Bridge } from "@/event-v2-bridge"
import { EventV2 } from "@opencode-ai/core/event"
import { Project } from "@opencode-ai/schema/project"
import { SessionContextEpoch } from "@opencode-ai/core/session/context-epoch"
import path from "path"
export const Info = Project.Info
export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
@@ -229,12 +231,19 @@ export const layer = Layer.effect(
sandboxes: [] as string[],
time: { created: Date.now(), updated: Date.now() },
}
const previousWorktree =
row &&
projectID !== ProjectV2.ID.global &&
existing.worktree !== worktree &&
!(yield* fs.isDir(existing.worktree))
? existing.worktree
: undefined
if (flags.experimentalIconDiscovery) yield* discover(existing).pipe(Effect.ignore, Effect.forkIn(scope))
const result: Info = {
...existing,
worktree: projectID === ProjectV2.ID.global ? worktree : existing.worktree,
worktree: projectID === ProjectV2.ID.global || previousWorktree ? worktree : existing.worktree,
vcs: data.vcs?.type ?? fakeVcs,
time: { ...existing.time, updated: Date.now() },
}
@@ -252,7 +261,11 @@ export const layer = Layer.effect(
Effect.map((exists) => (exists ? s : undefined)),
),
{ concurrency: "unbounded" },
).pipe(Effect.map((arr) => arr.filter((x): x is string => x !== undefined)))
).pipe(
Effect.map((arr) =>
arr.filter((x): x is string => x !== undefined && x !== result.worktree && x !== previousWorktree),
),
)
yield* db
.insert(ProjectTable)
@@ -288,6 +301,29 @@ export const layer = Layer.effect(
.run()
.pipe(Effect.orDie)
if (previousWorktree) {
const sessions = yield* db
.select({ id: SessionTable.id, directory: SessionTable.directory })
.from(SessionTable)
.where(eq(SessionTable.project_id, projectID))
.all()
.pipe(Effect.orDie)
yield* Effect.forEach(
sessions.filter((session) => FSUtil.contains(previousWorktree, session.directory)),
(session) =>
Effect.gen(function* () {
yield* db
.update(SessionTable)
.set({ directory: path.join(result.worktree, path.relative(previousWorktree, session.directory)) })
.where(eq(SessionTable.id, session.id))
.run()
.pipe(Effect.orDie)
yield* SessionContextEpoch.reset(db, session.id)
}),
{ concurrency: 1, discard: true },
)
}
if (projectID !== ProjectV2.ID.global) {
yield* db
.update(SessionTable)
@@ -301,6 +337,9 @@ export const layer = Layer.effect(
projectID,
directory: data.directory,
})
if (previousWorktree) {
yield* projectDirectories.remove({ projectID, directory: AbsolutePath.make(previousWorktree) })
}
yield* emitUpdated(result)
if (projectID !== ProjectV2.ID.global && data.vcs?.type === "git") {
@@ -334,7 +373,16 @@ export const layer = Layer.effect(
})
const list = Effect.fn("Project.list")(function* () {
return (yield* db.select().from(ProjectTable).all().pipe(Effect.orDie)).map(fromRow)
const projects = (yield* db.select().from(ProjectTable).all().pipe(Effect.orDie)).map(fromRow)
return (yield* Effect.forEach(
projects,
Effect.fnUntraced(function* (project) {
if (project.id === ProjectV2.ID.global) return project
if (yield* fs.isDir(project.worktree)) return project
return undefined
}),
{ concurrency: "unbounded" },
)).filter((project): project is Info => project !== undefined)
})
const get = Effect.fn("Project.get")(function* (id: ProjectV2.ID) {
+83 -1
View File
@@ -6,7 +6,7 @@ import path from "path"
import { tmpdirScoped } from "../fixture/fixture"
import { GlobalBus } from "../../src/bus/global"
import { Database } from "@opencode-ai/core/database/database"
import { ProjectTable } from "@opencode-ai/core/project/sql"
import { ProjectDirectoryTable, ProjectTable } from "@opencode-ai/core/project/sql"
import { SessionTable } from "@opencode-ai/core/session/sql"
import { WorkspaceTable } from "@opencode-ai/core/control-plane/workspace.sql"
import { eq } from "drizzle-orm"
@@ -23,6 +23,7 @@ import { ProjectDirectories } from "@opencode-ai/core/project/directories"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { testEffect } from "../lib/effect"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { AbsolutePath } from "@opencode-ai/core/schema"
const encoder = new TextEncoder()
@@ -403,6 +404,87 @@ describe("Project.fromDirectory with worktrees", () => {
expect(result.project.sandboxes).not.toContain(tmp)
}),
)
it.live("relocates a project and its sessions when the primary checkout moved", () =>
Effect.gen(function* () {
const { db } = yield* Database.Service
const project = yield* Project.Service
const tmp = yield* tmpdirScoped({ git: true })
const original = yield* project.fromDirectory(tmp)
const moved = `${tmp}-moved`
const rootSession = SessionID.make(`ses_${crypto.randomUUID()}`)
const nestedSession = SessionID.make(`ses_${crypto.randomUUID()}`)
yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${moved}`.quiet().nothrow()).pipe(Effect.ignore))
yield* db
.insert(SessionTable)
.values([
{
id: rootSession,
project_id: original.project.id,
slug: rootSession,
directory: tmp,
title: "root",
version: "test",
time_created: 1,
time_updated: 1,
},
{
id: nestedSession,
project_id: original.project.id,
slug: nestedSession,
directory: path.join(tmp, "packages", "app"),
path: "packages/app",
title: "nested",
version: "test",
time_created: 2,
time_updated: 2,
},
])
.run()
.pipe(Effect.orDie)
yield* Effect.promise(() => $`mv ${tmp} ${moved}`.quiet())
const result = yield* project.fromDirectory(moved)
const sessions = yield* db
.select({ id: SessionTable.id, directory: SessionTable.directory, path: SessionTable.path })
.from(SessionTable)
.where(eq(SessionTable.project_id, original.project.id))
.all()
.pipe(Effect.orDie)
const directories = yield* db
.select({ directory: ProjectDirectoryTable.directory })
.from(ProjectDirectoryTable)
.where(eq(ProjectDirectoryTable.project_id, original.project.id))
.all()
.pipe(Effect.orDie)
expect(result.project.worktree).toBe(moved)
expect(result.project.sandboxes).not.toContain(tmp)
expect(result.project.sandboxes).not.toContain(moved)
expect(directories.map((item) => item.directory)).toEqual([AbsolutePath.make(moved)])
expect(sessions).toContainEqual({ id: rootSession, directory: moved, path: null })
expect(sessions).toContainEqual({
id: nestedSession,
directory: path.join(moved, "packages", "app"),
path: "packages/app",
})
}),
)
it.live("omits projects whose primary checkout no longer exists", () =>
Effect.gen(function* () {
const project = yield* Project.Service
const tmp = yield* tmpdirScoped({ git: true })
const original = yield* project.fromDirectory(tmp)
const moved = `${tmp}-moved`
yield* Effect.addFinalizer(() => Effect.promise(() => $`rm -rf ${moved}`.quiet().nothrow()).pipe(Effect.ignore))
yield* Effect.promise(() => $`mv ${tmp} ${moved}`.quiet())
const result = (yield* project.list()).find((item) => item.id === original.project.id)
expect(result).toBeUndefined()
}),
)
})
describe("Project.discover", () => {
@@ -1067,6 +1067,40 @@ const scenarios: Scenario[] = [
headers: ctx.headers(),
}))
.status(400, undefined, "none"),
http.protected
.get("/api/session/{sessionID}/history", "v2.session.history")
.seeded((ctx) => ctx.session({ title: "Session history" }))
.at((ctx) => ({
path: `${route("/api/session/{sessionID}/history", { sessionID: ctx.state.id })}?${new URLSearchParams({
after: "0",
limit: "2",
})}`,
headers: ctx.headers(),
}))
.json(
200,
(body) => {
object(body)
array(body.data)
check(typeof body.hasMore === "boolean", "Expected a history exhaustion signal")
},
"none",
),
http.protected
.get("/api/session/{sessionID}/history", "v2.session.history.missing")
.at((ctx) => ({
path: route("/api/session/{sessionID}/history", { sessionID: "ses_httpapi_missing" }),
headers: ctx.headers(),
}))
.json(404, object, "status"),
http.protected
.get("/api/session/{sessionID}/history", "v2.session.history.invalid")
.seeded((ctx) => ctx.session({ title: "Invalid history sequence" }))
.at((ctx) => ({
path: `${route("/api/session/{sessionID}/history", { sessionID: ctx.state.id })}?after=-1`,
headers: ctx.headers(),
}))
.json(400, object, "status"),
http.protected
.get("/api/session/{sessionID}/event", "v2.session.events.missing")
.at((ctx) => ({
@@ -27,13 +27,44 @@ const Event = Schema.Struct({
data: Schema.Unknown,
})
async function readEvent(reader: ReadableStreamDefaultReader<Uint8Array>) {
const value = await reader.read()
if (value.done) throw new Error("event stream closed")
return Schema.decodeUnknownSync(Event)(JSON.parse(new TextDecoder().decode(value.value).replace(/^data: /, "")))
async function* eventStream(body: ReadableStream<Uint8Array>) {
const reader = body.getReader()
const decoder = new TextDecoder()
let buffer = ""
try {
while (true) {
const boundary = buffer.match(/(?:\r\n|\r|\n){2}/)
if (!boundary || boundary.index === undefined) {
const value = await reader.read()
if (value.done) return
buffer += decoder.decode(value.value, { stream: true })
continue
}
const record = buffer.slice(0, boundary.index)
buffer = buffer.slice(boundary.index + boundary[0].length)
const data = record
.split(/\r\n|\r|\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).replace(/^ /, ""))
if (data.length) yield Schema.decodeUnknownSync(Event)(JSON.parse(data.join("\n")))
}
} finally {
try {
await reader.cancel()
} finally {
reader.releaseLock()
}
}
}
async function readEventType(reader: ReadableStreamDefaultReader<Uint8Array>, type: string) {
async function readEvent(reader: AsyncIterator<typeof Event.Type>) {
const value = await reader.next()
if (value.done) throw new Error("event stream closed")
return value.value
}
async function readEventType(reader: AsyncIterator<typeof Event.Type>, type: string) {
for (let index = 0; index < 20; index++) {
const event = await readEvent(reader)
if (event.type === type) return event
@@ -78,7 +109,7 @@ describe("v2 location HttpApi", () => {
await using subscriber = await tmpdir({ git: true })
await using publisher = await tmpdir({ git: true })
const response = await request("/api/event", subscriber.path)
const reader = response.body!.getReader()
const reader = eventStream(response.body!)
const connected = await readEvent(reader)
expect(connected.type).toBe("server.connected")
expect(connected.location).toBeUndefined()
@@ -90,6 +121,6 @@ describe("v2 location HttpApi", () => {
location: { directory: publisher.path },
data: { sessionID: expect.any(String) },
})
await reader.cancel()
await reader.return(undefined)
})
})
+10 -13
View File
@@ -3,7 +3,7 @@ import { EventManifest } from "@opencode-ai/schema/event-manifest"
import { Location } from "@opencode-ai/schema/location"
import type { Definition } from "@opencode-ai/schema/event"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
const fields = {
id: Event.ID,
@@ -12,15 +12,9 @@ const fields = {
location: Schema.optional(Location.Ref),
}
const schema = (definitions: ReadonlyArray<Definition>) =>
const schema = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
Schema.Union([
...definitions.map((definition) =>
Schema.Struct({
...fields,
type: Schema.Literal(definition.type),
data: definition.data,
}).annotate({ identifier: `V2Event.${definition.type}` }),
),
...definitions,
...(definitions.some((definition) => definition.type === "server.connected")
? []
: [
@@ -32,14 +26,14 @@ const schema = (definitions: ReadonlyArray<Definition>) =>
]),
]).annotate({ identifier: "V2Event" })
const make = (definitions: ReadonlyArray<Definition>) => {
const make = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) => {
const EventSchema = schema(definitions)
return {
schema: EventSchema,
group: HttpApiGroup.make("server.event")
.add(
HttpApiEndpoint.get("event.subscribe", "/api/event", {
success: EventSchema,
success: HttpApiSchema.StreamSse({ data: EventSchema }),
}).annotateMerge(
OpenApi.annotations({
identifier: "v2.event.subscribe",
@@ -52,8 +46,11 @@ const make = (definitions: ReadonlyArray<Definition>) => {
}
}
export const makeEventGroup = (definitions: ReadonlyArray<Definition>) => make(definitions).group
export const makeEventGroup = <const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) =>
make(definitions).group
const event = make(EventManifest.ServerDefinitions)
export const EventGroup = event.group
export type Event = typeof event.schema.Type
export const OpenCodeEvent = event.schema
export type OpenCodeEvent = typeof OpenCodeEvent.Type
export type OpenCodeEventEncoded = typeof OpenCodeEvent.Encoded
+36 -2
View File
@@ -5,7 +5,7 @@ import { Session } from "@opencode-ai/schema/session"
import { Project } from "@opencode-ai/schema/project"
import { AbsolutePath, NonNegativeInt, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
import { Workspace } from "@opencode-ai/schema/workspace"
import { Context, Encoding, Result, Schema, Struct } from "effect"
import { Context, Effect, Encoding, Result, Schema, Struct } from "effect"
import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import {
ConflictError,
@@ -60,6 +60,7 @@ const SessionsCursorInput = Schema.Union([
const SessionsCursorJson = Schema.fromJsonString(SessionsCursorInput)
const encodeSessionsCursor = Schema.encodeSync(SessionsCursorJson)
const decodeSessionsCursor = Schema.decodeUnknownEffect(SessionsCursorJson)
const invalidCursor = "Invalid cursor" as const
export const SessionsCursor = Schema.String.pipe(
Schema.brand("SessionsCursor"),
@@ -67,7 +68,13 @@ export const SessionsCursor = Schema.String.pipe(
const make = schema.make.bind(schema)
return {
make: (input: typeof SessionsCursorInput.Type) => make(Encoding.encodeBase64Url(encodeSessionsCursor(input))),
parse: (input: string) => decodeSessionsCursor(Result.getOrThrow(Encoding.decodeBase64UrlString(input))),
parse: (input: string) =>
Effect.suspend(() => {
const result = Encoding.decodeBase64UrlString(input)
return Result.isFailure(result)
? Effect.fail(invalidCursor)
: decodeSessionsCursor(result.success).pipe(Effect.mapError(() => invalidCursor))
}),
}
}),
)
@@ -77,6 +84,13 @@ const SessionActive = Schema.Struct({
type: Schema.Literal("running"),
}).annotate({ identifier: "SessionActive" })
const SessionHistoryLimit = PositiveInt.check(Schema.isLessThanOrEqualTo(100))
export const SessionHistoryQuery = Schema.Struct({
limit: Schema.NumberFromString.pipe(Schema.decodeTo(SessionHistoryLimit), Schema.optional),
after: Schema.NumberFromString.pipe(Schema.decodeTo(NonNegativeInt), Schema.optional),
})
const SessionsQueryCursor = SessionsCursor.annotate({
description: "Opaque pagination cursor returned as cursor.previous or cursor.next in the previous response.",
})
@@ -289,6 +303,26 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
}),
),
)
.add(
HttpApiEndpoint.get("session.history", "/api/session/:sessionID/history", {
params: { sessionID: Session.ID },
query: SessionHistoryQuery,
success: Schema.Struct({
data: Schema.Array(SessionEvent.Durable),
hasMore: Schema.Boolean,
}).annotate({ identifier: "SessionHistory" }),
error: SessionNotFoundError,
})
.middleware(sessionLocationMiddleware)
.annotateMerge(
OpenApi.annotations({
identifier: "v2.session.history",
summary: "Get session history",
description:
"Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages.",
}),
),
)
.add(
HttpApiEndpoint.get("session.events", "/api/session/:sessionID/event", {
params: { sessionID: Session.ID },
+10 -2
View File
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { SessionsCursor } from "../src/groups/session"
import { Effect, Schema } from "effect"
import { SessionHistoryQuery, SessionsCursor } from "../src/groups/session"
import { Session } from "@opencode-ai/schema/session"
describe("SessionsCursor", () => {
@@ -16,3 +16,11 @@ describe("SessionsCursor", () => {
expect(await Effect.runPromise(SessionsCursor.parse(cursor))).toEqual(input)
})
})
describe("SessionHistoryQuery", () => {
test("decodes numeric paging inputs", async () => {
const query = await Effect.runPromise(Schema.decodeUnknownEffect(SessionHistoryQuery)({ after: "3", limit: "10" }))
expect(query).toEqual({ after: 3, limit: 10 })
})
})
@@ -4,6 +4,11 @@ import { Event } from "./event"
import { SessionEvent } from "./session-event"
import { SessionV1 } from "./session-v1"
export const SessionDurable = {
definitions: Event.durable(SessionEvent.DurableDefinitions),
schema: SessionEvent.Durable,
} as const
export const Durable = Event.durable([
...SessionV1.Event.Definitions.filter((definition) => definition.durable !== undefined),
...SessionEvent.DurableDefinitions,
+3 -3
View File
@@ -55,7 +55,7 @@ export function define<
id: ID,
metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
type: Schema.Literal(input.type),
durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Number, version: Schema.Number })),
durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
location: optional(Location.Ref),
data,
})
@@ -95,7 +95,7 @@ export function versionedType(type: string, version: number) {
return `${type}.${version}`
}
export function durable(definitions: ReadonlyArray<Definition>) {
export function durable<const Definitions extends ReadonlyArray<Definition>>(definitions: Definitions) {
return readonlyMap(
definitions.reduce((result, definition) => {
if (!definition.durable) return result
@@ -103,7 +103,7 @@ export function durable(definitions: ReadonlyArray<Definition>) {
if (result.has(key)) throw new Error(`Duplicate durable event definition for ${key}`)
result.set(key, definition)
return result
}, new Map<string, Definition>()),
}, new Map<string, Definitions[number]>()),
)
}
+3 -1
View File
@@ -511,7 +511,9 @@ export const Definitions = Event.inventory(
RevertEvent.Committed,
)
export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
export const Durable = Schema.Union(DurableDefinitions, { mode: "oneOf" })
.pipe(Schema.toTaggedUnion("type"))
.annotate({ identifier: "SessionDurableEvent" })
export type DurableEvent = typeof Durable.Type
export const All = Schema.Union(Definitions, { mode: "oneOf" }).pipe(Schema.toTaggedUnion("type"))
+1
View File
@@ -14,3 +14,4 @@ export {
SessionInput,
SessionMessage,
} from "@opencode-ai/client/effect"
export type { OpenCodeEvent } from "@opencode-ai/client/effect"
+84 -1
View File
@@ -3,7 +3,8 @@ import { mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { Flag } from "@opencode-ai/core/flag/flag"
import { Effect, Option, Schema, Stream } from "effect"
import { Deferred, Effect, Latch, Option, Schema, Stream } from "effect"
import type { OpenCodeEvent } from "../src"
test("embedded client uses the real router and handlers", async () => {
const directory = await mkdtemp(join(tmpdir(), "opencode-embedded-"))
@@ -103,6 +104,88 @@ test("embedded client uses the real router and handlers", async () => {
}
})
test("Location-owned runner events reach the ready global client", async () => {
const directory = await mkdtemp(join(tmpdir(), "opencode-embedded-events-"))
const database = Flag.OPENCODE_DB
Flag.OPENCODE_DB = join(directory, "opencode.sqlite")
const { AbsolutePath, Location, OpenCode, Prompt, Session } = await import("../src")
const sessionID = Session.ID.make(`ses_embedded_${crypto.randomUUID()}`)
try {
const program = Effect.gen(function* () {
const opencode = yield* OpenCode.create()
const connected = yield* Latch.make(false)
const prompted = yield* Deferred.make<OpenCodeEvent>()
yield* opencode.events.subscribe().pipe(
Stream.runForEach((event) =>
event.type === "server.connected"
? connected.open
: event.type === "session.next.prompted" && event.data.sessionID === sessionID
? Deferred.succeed(prompted, event).pipe(Effect.asVoid)
: Effect.void,
),
Effect.forkScoped,
)
yield* connected.await
yield* opencode.sessions.create({
id: sessionID,
location: Location.Ref.make({ directory: AbsolutePath.make(directory) }),
})
yield* opencode.sessions.prompt({ sessionID, prompt: Prompt.make({ text: "Observe this input" }) })
const event = yield* Deferred.await(prompted).pipe(Effect.timeout("4 seconds"))
expect(event.durable).toEqual(expect.objectContaining({ aggregateID: sessionID, seq: expect.any(Number) }))
})
await Effect.runPromise(Effect.scoped(program))
} finally {
Flag.OPENCODE_DB = database
await rm(directory, { recursive: true, force: true })
}
}, 10_000)
test("independent embedded hosts do not share live notifications", async () => {
const directory = await mkdtemp(join(tmpdir(), "opencode-embedded-hosts-"))
const database = Flag.OPENCODE_DB
Flag.OPENCODE_DB = join(directory, "opencode.sqlite")
const { AbsolutePath, Agent, Location, OpenCode, Session } = await import("../src")
const sessionID = Session.ID.make(`ses_embedded_${crypto.randomUUID()}`)
try {
const program = Effect.gen(function* () {
const first = yield* OpenCode.create()
const second = yield* OpenCode.create()
const firstReady = yield* Latch.make(false)
const secondReady = yield* Latch.make(false)
const firstEvent = yield* Latch.make(false)
const secondEvent = yield* Latch.make(false)
const observe = (ready: Latch.Latch, event: Latch.Latch) =>
Stream.runForEach((notification: OpenCodeEvent) =>
notification.type === "server.connected"
? ready.open
: notification.type === "session.next.agent.switched" && notification.data.sessionID === sessionID
? event.open
: Effect.void,
)
yield* first.events.subscribe().pipe(observe(firstReady, firstEvent), Effect.forkScoped)
yield* second.events.subscribe().pipe(observe(secondReady, secondEvent), Effect.forkScoped)
yield* Effect.all([firstReady.await, secondReady.await], { discard: true })
yield* first.sessions.create({
id: sessionID,
location: Location.Ref.make({ directory: AbsolutePath.make(directory) }),
})
yield* first.sessions.switchAgent({ sessionID, agent: Agent.ID.make("plan") })
yield* firstEvent.await.pipe(Effect.timeout("2 seconds"))
expect(Option.isNone(yield* secondEvent.await.pipe(Effect.timeoutOption("100 millis")))).toBe(true)
})
await Effect.runPromise(Effect.scoped(program))
} finally {
Flag.OPENCODE_DB = database
await rm(directory, { recursive: true, force: true })
}
}, 10_000)
test("embedded client is available as a Layer service", async () => {
const directory = await mkdtemp(join(tmpdir(), "opencode-embedded-layer-"))
const database = Flag.OPENCODE_DB
+1
View File
@@ -5,6 +5,7 @@
"type": "module",
"license": "MIT",
"scripts": {
"test": "bun test",
"typecheck": "tsgo --noEmit",
"build": "bun ./script/build.ts"
},
+54
View File
@@ -13,6 +13,37 @@ const opencode = path.resolve(dir, "../../opencode")
await $`bun dev generate > ${dir}/openapi.json`.cwd(opencode)
const document = (await Bun.file("./openapi.json").json()) as {
components?: { schemas?: Record<string, unknown> }
[key: string]: unknown
}
const schemas = document.components?.schemas
if (schemas) {
const reachable = new Set<string>()
const visit = (value: unknown) => {
if (Array.isArray(value)) {
value.forEach(visit)
return
}
if (typeof value !== "object" || value === null) return
for (const [key, child] of Object.entries(value)) {
if (key === "$ref" && typeof child === "string" && child.startsWith("#/components/schemas/")) {
const name = child.slice("#/components/schemas/".length)
if (reachable.has(name)) continue
reachable.add(name)
visit(schemas[name])
} else {
visit(child)
}
}
}
visit({ ...document, components: { ...document.components, schemas: undefined } })
for (const name of Object.keys(schemas)) {
if (/^SessionNext\w+1$/.test(name) && !reachable.has(name)) delete schemas[name]
}
await Bun.write("./openapi.json", JSON.stringify(document))
}
await createClient({
input: "./openapi.json",
output: {
@@ -40,6 +71,29 @@ await createClient({
],
})
const generatedTypes = await Bun.file("./src/v2/gen/types.gen.ts").text()
if (/export type SessionNext\w+1 =/.test(generatedTypes)) {
throw new Error("Session history generated duplicate Session event variants")
}
const historyTypesPatched = generatedTypes.replace(
/(export type V2SessionHistoryData = \{[\s\S]*?query\?: \{\s*limit\?: )string([;,]\s*after\?: )string/,
"$1number$2number",
)
if (historyTypesPatched === generatedTypes) {
throw new Error("Session history numeric query patch did not apply")
}
await Bun.write("./src/v2/gen/types.gen.ts", historyTypesPatched)
const generatedSdk = await Bun.file("./src/v2/gen/sdk.gen.ts").text()
const historySdkPatched = generatedSdk.replace(
/(Get session history[\s\S]*?parameters: \{\s*sessionID: string[;,]\s*limit\?: )string([;,]\s*after\?: )string/,
"$1number$2number",
)
if (historySdkPatched === generatedSdk) {
throw new Error("Session history numeric SDK patch did not apply")
}
await Bun.write("./src/v2/gen/sdk.gen.ts", historySdkPatched)
// Patch a @hey-api/openapi-ts codegen bug: SseFn incorrectly passes the
// endpoint's TError into the second generic of ServerSentEventsResult, which
// is the AsyncGenerator's TReturn slot. Iterator return values have nothing
+34
View File
@@ -345,6 +345,8 @@ import type {
V2SessionEventsResponses,
V2SessionGetErrors,
V2SessionGetResponses,
V2SessionHistoryErrors,
V2SessionHistoryResponses,
V2SessionInterruptErrors,
V2SessionInterruptResponses,
V2SessionListErrors,
@@ -5710,6 +5712,38 @@ export class Session3 extends HeyApiClient {
})
}
/**
* Get session history
*
* Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages.
*/
public history<ThrowOnError extends boolean = false>(
parameters: {
sessionID: string
limit?: number
after?: number
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "path", key: "sessionID" },
{ in: "query", key: "limit" },
{ in: "query", key: "after" },
],
},
],
)
return (options?.client ?? this.client).get<V2SessionHistoryResponses, V2SessionHistoryErrors, ThrowOnError>({
url: "/api/session/{sessionID}/history",
...options,
...params,
})
}
/**
* Subscribe to session events
*
+556 -1078
View File
@@ -2731,6 +2731,43 @@ export type UnknownError1 = {
ref?: string
}
export type SessionDurableEvent =
| SessionNextAgentSwitched
| SessionNextModelSwitched
| SessionNextMoved
| SessionNextPrompted
| SessionNextPromptAdmitted
| SessionNextContextUpdated
| SessionNextSynthetic
| SessionNextShellStarted
| SessionNextShellEnded
| SessionNextStepStarted
| SessionNextStepEnded
| SessionNextStepFailed
| SessionNextTextStarted
| SessionNextTextEnded
| SessionNextToolInputStarted
| SessionNextToolInputEnded
| SessionNextToolCalled
| SessionNextToolProgress
| SessionNextToolSuccess
| SessionNextToolFailed
| SessionNextReasoningStarted
| SessionNextReasoningEnded
| SessionNextRetried
| SessionNextCompactionStarted
| SessionNextCompactionEnded
| SessionNextRevertStaged
| SessionNextRevertCleared
| SessionNextRevertCommitted
export type SessionHistory = {
data: Array<SessionDurableEvent>
hasMore: boolean
}
export type SessionDurableEvent1 = string
export type SessionMessagesResponse = {
data: Array<SessionMessage>
cursor: {
@@ -2745,95 +2782,162 @@ export type ProviderNotFoundError = {
message: string
}
export type V2Event =
| V2EventModelsDevRefreshed
| V2EventIntegrationUpdated
| V2EventIntegrationConnectionUpdated
| V2EventCatalogUpdated
| V2EventSessionCreated
| V2EventSessionUpdated
| V2EventSessionDeleted
| V2EventMessageUpdated
| V2EventMessageRemoved
| V2EventMessagePartUpdated
| V2EventMessagePartRemoved
| V2EventSessionNextAgentSwitched
| V2EventSessionNextModelSwitched
| V2EventSessionNextMoved
| V2EventSessionNextPrompted
| V2EventSessionNextPromptAdmitted
| V2EventSessionNextContextUpdated
| V2EventSessionNextSynthetic
| V2EventSessionNextShellStarted
| V2EventSessionNextShellEnded
| V2EventSessionNextStepStarted
| V2EventSessionNextStepEnded
| V2EventSessionNextStepFailed
| V2EventSessionNextTextStarted
| V2EventSessionNextTextDelta
| V2EventSessionNextTextEnded
| V2EventSessionNextReasoningStarted
| V2EventSessionNextReasoningDelta
| V2EventSessionNextReasoningEnded
| V2EventSessionNextToolInputStarted
| V2EventSessionNextToolInputDelta
| V2EventSessionNextToolInputEnded
| V2EventSessionNextToolCalled
| V2EventSessionNextToolProgress
| V2EventSessionNextToolSuccess
| V2EventSessionNextToolFailed
| V2EventSessionNextRetried
| V2EventSessionNextCompactionStarted
| V2EventSessionNextCompactionDelta
| V2EventSessionNextCompactionEnded
| V2EventSessionNextRevertStaged
| V2EventSessionNextRevertCleared
| V2EventSessionNextRevertCommitted
| V2EventMessagePartDelta
| V2EventSessionDiff
| V2EventSessionError
| V2EventInstallationUpdated
| V2EventInstallationUpdateAvailable
| V2EventFileEdited
| V2EventReferenceUpdated
| V2EventPermissionV2Asked
| V2EventPermissionV2Replied
| V2EventPluginAdded
| V2EventProjectDirectoriesUpdated
| V2EventFileWatcherUpdated
| V2EventPtyCreated
| V2EventPtyUpdated
| V2EventPtyExited
| V2EventPtyDeleted
| V2EventQuestionV2Asked
| V2EventQuestionV2Replied
| V2EventQuestionV2Rejected
| V2EventTodoUpdated
| V2EventLspUpdated
| V2EventPermissionAsked
| V2EventPermissionReplied
| V2EventTuiPromptAppend
| V2EventTuiCommandExecute
| V2EventTuiToastShow
| V2EventTuiSessionSelect
| V2EventMcpToolsChanged
| V2EventMcpBrowserOpenFailed
| V2EventCommandExecuted
| V2EventProjectUpdated
| V2EventSessionStatus
| V2EventSessionIdle
| V2EventQuestionAsked
| V2EventQuestionReplied
| V2EventQuestionRejected
| V2EventSessionCompacted
| V2EventVcsBranchUpdated
| V2EventWorkspaceReady
| V2EventWorkspaceFailed
| V2EventWorkspaceStatus
| V2EventWorktreeReady
| V2EventWorktreeFailed
| V2EventServerConnected
| V2EventGlobalDisposed
export type OutputFormat1 =
| {
type: "text"
}
| {
type: "json_schema"
schema: JsonSchema
retryCount?: number
}
export type SessionStatus2 = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.status"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
status: SessionStatus
}
}
export type QuestionReplied2 = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.replied"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
requestID: string
answers: Array<QuestionAnswer>
}
}
export type QuestionRejected2 = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.rejected"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
requestID: string
}
}
export type V2Event1 =
| ModelsDevRefreshed
| IntegrationUpdated
| IntegrationConnectionUpdated
| CatalogUpdated
| SessionCreated
| SessionUpdated
| SessionDeleted
| MessageUpdated
| MessageRemoved
| MessagePartUpdated
| MessagePartRemoved
| SessionNextAgentSwitched
| SessionNextModelSwitched
| SessionNextMoved
| SessionNextPrompted
| SessionNextPromptAdmitted
| SessionNextContextUpdated
| SessionNextSynthetic
| SessionNextShellStarted
| SessionNextShellEnded
| SessionNextStepStarted
| SessionNextStepEnded
| SessionNextStepFailed
| SessionNextTextStarted
| SessionNextTextDelta
| SessionNextTextEnded
| SessionNextReasoningStarted
| SessionNextReasoningDelta
| SessionNextReasoningEnded
| SessionNextToolInputStarted
| SessionNextToolInputDelta
| SessionNextToolInputEnded
| SessionNextToolCalled
| SessionNextToolProgress
| SessionNextToolSuccess
| SessionNextToolFailed
| SessionNextRetried
| SessionNextCompactionStarted
| SessionNextCompactionDelta
| SessionNextCompactionEnded
| SessionNextRevertStaged
| SessionNextRevertCleared
| SessionNextRevertCommitted
| MessagePartDelta
| SessionDiff
| SessionError
| InstallationUpdated
| InstallationUpdateAvailable
| FileEdited
| ReferenceUpdated
| PermissionV2Asked
| PermissionV2Replied
| PluginAdded
| ProjectDirectoriesUpdated
| FileWatcherUpdated
| PtyCreated
| PtyUpdated
| PtyExited
| PtyDeleted
| QuestionV2Asked
| QuestionV2Replied
| QuestionV2Rejected
| TodoUpdated
| LspUpdated
| PermissionAsked
| PermissionReplied
| TuiPromptAppend
| TuiCommandExecute
| TuiToastShow
| TuiSessionSelect
| McpToolsChanged
| McpBrowserOpenFailed
| CommandExecuted
| ProjectUpdated
| SessionStatus2
| SessionIdle
| QuestionAsked
| QuestionReplied2
| QuestionRejected2
| SessionCompacted
| VcsBranchUpdated
| WorkspaceReady
| WorkspaceFailed
| WorkspaceStatus
| WorktreeReady
| WorktreeFailed
| ServerConnected
| GlobalDisposed
export type V2Event = string
export type ForbiddenError = {
_tag: "ForbiddenError"
@@ -4057,8 +4161,8 @@ export type SessionNextAgentSwitched = {
type: "session.next.agent.switched"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4077,8 +4181,8 @@ export type SessionNextModelSwitched = {
type: "session.next.model.switched"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4097,8 +4201,8 @@ export type SessionNextMoved = {
type: "session.next.moved"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4117,8 +4221,8 @@ export type SessionNextPrompted = {
type: "session.next.prompted"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4138,8 +4242,8 @@ export type SessionNextPromptAdmitted = {
type: "session.next.prompt.admitted"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4159,8 +4263,8 @@ export type SessionNextContextUpdated = {
type: "session.next.context.updated"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4179,8 +4283,8 @@ export type SessionNextSynthetic = {
type: "session.next.synthetic"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4199,8 +4303,8 @@ export type SessionNextShellStarted = {
type: "session.next.shell.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4220,8 +4324,8 @@ export type SessionNextShellEnded = {
type: "session.next.shell.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4240,8 +4344,8 @@ export type SessionNextStepStarted = {
type: "session.next.step.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4262,8 +4366,8 @@ export type SessionNextStepEnded = {
type: "session.next.step.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4294,8 +4398,8 @@ export type SessionNextStepFailed = {
type: "session.next.step.failed"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4314,8 +4418,8 @@ export type SessionNextTextStarted = {
type: "session.next.text.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4334,8 +4438,8 @@ export type SessionNextTextEnded = {
type: "session.next.text.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4355,8 +4459,8 @@ export type SessionNextToolInputStarted = {
type: "session.next.tool.input.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4376,8 +4480,8 @@ export type SessionNextToolInputEnded = {
type: "session.next.tool.input.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4397,8 +4501,8 @@ export type SessionNextToolCalled = {
type: "session.next.tool.called"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4425,8 +4529,8 @@ export type SessionNextToolProgress = {
type: "session.next.tool.progress"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4449,8 +4553,8 @@ export type SessionNextToolSuccess = {
type: "session.next.tool.success"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4479,8 +4583,8 @@ export type SessionNextToolFailed = {
type: "session.next.tool.failed"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4505,8 +4609,8 @@ export type SessionNextReasoningStarted = {
type: "session.next.reasoning.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4526,8 +4630,8 @@ export type SessionNextReasoningEnded = {
type: "session.next.reasoning.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4548,8 +4652,8 @@ export type SessionNextRetried = {
type: "session.next.retried"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4568,8 +4672,8 @@ export type SessionNextCompactionStarted = {
type: "session.next.compaction.started"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4588,8 +4692,8 @@ export type SessionNextCompactionEnded = {
type: "session.next.compaction.ended"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4610,8 +4714,8 @@ export type SessionNextRevertStaged = {
type: "session.next.revert.staged"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4629,8 +4733,8 @@ export type SessionNextRevertCleared = {
type: "session.next.revert.cleared"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4647,8 +4751,8 @@ export type SessionNextRevertCommitted = {
type: "session.next.revert.committed"
durable?: {
aggregateID: string
seq: number | "NaN" | "Infinity" | "-Infinity"
version: number | "NaN" | "Infinity" | "-Infinity"
seq: number
version: number
}
location?: LocationRef
data: {
@@ -4908,176 +5012,176 @@ export type SkillV2Info = {
content: string
}
export type V2EventModelsDevRefreshed = {
export type ModelsDevRefreshed = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "models-dev.refreshed"
data: {
[key: string]: unknown
}
}
export type V2EventIntegrationUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
[key: string]: unknown
}
}
export type IntegrationUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "integration.updated"
data: {
[key: string]: unknown
}
}
export type V2EventIntegrationConnectionUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
[key: string]: unknown
}
}
export type IntegrationConnectionUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "integration.connection.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
integrationID: string
}
}
export type V2EventCatalogUpdated = {
export type CatalogUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "catalog.updated"
data: {
[key: string]: unknown
}
}
export type V2EventSessionCreated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
[key: string]: unknown
}
}
export type SessionCreated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.created"
data: {
sessionID: string
info: Session
}
}
export type V2EventSessionUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
info: Session
}
}
export type SessionUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.updated"
data: {
sessionID: string
info: Session
}
}
export type V2EventSessionDeleted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
info: Session
}
}
export type SessionDeleted = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.deleted"
data: {
sessionID: string
info: Session
}
}
export type V2EventMessageUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
info: Session
}
}
export type MessageUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "message.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
info: Message
}
}
export type V2EventMessageRemoved = {
export type MessageRemoved = {
id: string
metadata?: {
[key: string]: unknown
}
type: "message.removed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.removed"
data: {
sessionID: string
messageID: string
}
}
export type V2EventMessagePartUpdated = {
export type MessagePartUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "message.part.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.part.updated"
data: {
sessionID: string
part: Part
@@ -5085,18 +5189,18 @@ export type V2EventMessagePartUpdated = {
}
}
export type V2EventMessagePartRemoved = {
export type MessagePartRemoved = {
id: string
metadata?: {
[key: string]: unknown
}
type: "message.part.removed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.part.removed"
data: {
sessionID: string
messageID: string
@@ -5104,295 +5208,18 @@ export type V2EventMessagePartRemoved = {
}
}
export type V2EventSessionNextAgentSwitched = {
export type SessionNextTextDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.agent.switched"
data: {
timestamp: number
sessionID: string
messageID: string
agent: string
}
}
export type V2EventSessionNextModelSwitched = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.model.switched"
data: {
timestamp: number
sessionID: string
messageID: string
model: ModelRef
}
}
export type V2EventSessionNextMoved = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.moved"
data: {
timestamp: number
sessionID: string
location: LocationRef
subdirectory?: string
}
}
export type V2EventSessionNextPrompted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.prompted"
data: {
timestamp: number
sessionID: string
messageID: string
prompt: Prompt
delivery: "steer" | "queue"
}
}
export type V2EventSessionNextPromptAdmitted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.prompt.admitted"
data: {
timestamp: number
sessionID: string
messageID: string
prompt: Prompt
delivery: "steer" | "queue"
}
}
export type V2EventSessionNextContextUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.context.updated"
data: {
timestamp: number
sessionID: string
messageID: string
text: string
}
}
export type V2EventSessionNextSynthetic = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.synthetic"
data: {
timestamp: number
sessionID: string
messageID: string
text: string
}
}
export type V2EventSessionNextShellStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.shell.started"
data: {
timestamp: number
sessionID: string
messageID: string
callID: string
command: string
}
}
export type V2EventSessionNextShellEnded = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.shell.ended"
data: {
timestamp: number
sessionID: string
callID: string
output: string
}
}
export type V2EventSessionNextStepStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.started"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
agent: string
model: ModelRef
snapshot?: string
}
}
export type V2EventSessionNextStepEnded = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.ended"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
finish: string
cost: number
tokens: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
snapshot?: string
files?: Array<string>
}
}
export type V2EventSessionNextStepFailed = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.failed"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
error: SessionErrorUnknown
}
}
export type V2EventSessionNextTextStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.text.started"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
textID: string
}
}
export type V2EventSessionNextTextDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.text.delta"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
timestamp: number
sessionID: string
@@ -5402,60 +5229,18 @@ export type V2EventSessionNextTextDelta = {
}
}
export type V2EventSessionNextTextEnded = {
export type SessionNextReasoningDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.text.ended"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
textID: string
text: string
}
}
export type V2EventSessionNextReasoningStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.reasoning.started"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
reasoningID: string
providerMetadata?: LlmProviderMetadata
}
}
export type V2EventSessionNextReasoningDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.reasoning.delta"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
timestamp: number
sessionID: string
@@ -5465,61 +5250,18 @@ export type V2EventSessionNextReasoningDelta = {
}
}
export type V2EventSessionNextReasoningEnded = {
export type SessionNextToolInputDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.reasoning.ended"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
reasoningID: string
text: string
providerMetadata?: LlmProviderMetadata
}
}
export type V2EventSessionNextToolInputStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.input.started"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
name: string
}
}
export type V2EventSessionNextToolInputDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.input.delta"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
timestamp: number
sessionID: string
@@ -5529,187 +5271,18 @@ export type V2EventSessionNextToolInputDelta = {
}
}
export type V2EventSessionNextToolInputEnded = {
export type SessionNextCompactionDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.input.ended"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
text: string
}
}
export type V2EventSessionNextToolCalled = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.called"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
tool: string
input: {
[key: string]: unknown
}
provider: {
executed: boolean
metadata?: LlmProviderMetadata
}
}
}
export type V2EventSessionNextToolProgress = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.progress"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
structured: {
[key: string]: unknown
}
content: Array<LlmToolContent>
}
}
export type V2EventSessionNextToolSuccess = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.success"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
structured: {
[key: string]: unknown
}
content: Array<LlmToolContent>
outputPaths?: Array<string>
result?: unknown
provider: {
executed: boolean
metadata?: LlmProviderMetadata
}
}
}
export type V2EventSessionNextToolFailed = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.failed"
data: {
timestamp: number
sessionID: string
assistantMessageID: string
callID: string
error: SessionErrorUnknown
result?: unknown
provider: {
executed: boolean
metadata?: LlmProviderMetadata
}
}
}
export type V2EventSessionNextRetried = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.retried"
data: {
timestamp: number
sessionID: string
attempt: number
error: SessionNextRetryError
}
}
export type V2EventSessionNextCompactionStarted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.compaction.started"
data: {
timestamp: number
sessionID: string
messageID: string
reason: "auto" | "manual"
}
}
export type V2EventSessionNextCompactionDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.compaction.delta"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
timestamp: number
sessionID: string
@@ -5718,96 +5291,18 @@ export type V2EventSessionNextCompactionDelta = {
}
}
export type V2EventSessionNextCompactionEnded = {
export type MessagePartDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.compaction.ended"
data: {
timestamp: number
sessionID: string
messageID: string
reason: "auto" | "manual"
text: string
recent: string
}
}
export type V2EventSessionNextRevertStaged = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.staged"
data: {
timestamp: number
sessionID: string
revert: RevertState
}
}
export type V2EventSessionNextRevertCleared = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.cleared"
data: {
timestamp: number
sessionID: string
}
}
export type V2EventSessionNextRevertCommitted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.committed"
data: {
timestamp: number
sessionID: string
messageID: string
}
}
export type V2EventMessagePartDelta = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.part.delta"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
messageID: string
@@ -5817,36 +5312,36 @@ export type V2EventMessagePartDelta = {
}
}
export type V2EventSessionDiff = {
export type SessionDiff = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.diff"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.diff"
data: {
sessionID: string
diff: Array<SnapshotFileDiff>
}
}
export type V2EventSessionError = {
export type SessionError = {
id: string
metadata?: {
[key: string]: unknown
}
type: "session.error"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.error"
data: {
sessionID?: string
error?:
@@ -5861,86 +5356,86 @@ export type V2EventSessionError = {
}
}
export type V2EventInstallationUpdated = {
export type InstallationUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "installation.updated"
data: {
version: string
}
}
export type V2EventInstallationUpdateAvailable = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
version: string
}
}
export type InstallationUpdateAvailable = {
id: string
metadata?: {
[key: string]: unknown
}
type: "installation.update-available"
data: {
version: string
}
}
export type V2EventFileEdited = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
version: string
}
}
export type FileEdited = {
id: string
metadata?: {
[key: string]: unknown
}
type: "file.edited"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
file: string
}
}
export type V2EventReferenceUpdated = {
export type ReferenceUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "reference.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "reference.updated"
data: {
[key: string]: unknown
}
}
export type V2EventPermissionV2Asked = {
export type PermissionV2Asked = {
id: string
metadata?: {
[key: string]: unknown
}
type: "permission.v2.asked"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "permission.v2.asked"
data: {
id: string
sessionID: string
@@ -5954,18 +5449,18 @@ export type V2EventPermissionV2Asked = {
}
}
export type V2EventPermissionV2Replied = {
export type PermissionV2Replied = {
id: string
metadata?: {
[key: string]: unknown
}
type: "permission.v2.replied"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "permission.v2.replied"
data: {
sessionID: string
requestID: string
@@ -5973,139 +5468,139 @@ export type V2EventPermissionV2Replied = {
}
}
export type V2EventPluginAdded = {
export type PluginAdded = {
id: string
metadata?: {
[key: string]: unknown
}
type: "plugin.added"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "plugin.added"
data: {
id: string
}
}
export type V2EventProjectDirectoriesUpdated = {
export type ProjectDirectoriesUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "project.directories.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "project.directories.updated"
data: {
projectID: string
}
}
export type V2EventFileWatcherUpdated = {
export type FileWatcherUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "file.watcher.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "file.watcher.updated"
data: {
file: string
event: "add" | "change" | "unlink"
}
}
export type V2EventPtyCreated = {
export type PtyCreated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "pty.created"
data: {
info: Pty
}
}
export type V2EventPtyUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
info: Pty
}
}
export type PtyUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "pty.updated"
data: {
info: Pty
}
}
export type V2EventPtyExited = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
info: Pty
}
}
export type PtyExited = {
id: string
metadata?: {
[key: string]: unknown
}
type: "pty.exited"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
id: string
exitCode: number
}
}
export type V2EventPtyDeleted = {
export type PtyDeleted = {
id: string
metadata?: {
[key: string]: unknown
}
type: "pty.deleted"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "pty.deleted"
data: {
id: string
}
}
export type V2EventQuestionV2Asked = {
export type QuestionV2Asked = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.v2.asked"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "question.v2.asked"
data: {
id: string
sessionID: string
@@ -6117,18 +5612,18 @@ export type V2EventQuestionV2Asked = {
}
}
export type V2EventQuestionV2Replied = {
export type QuestionV2Replied = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.v2.replied"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "question.v2.replied"
data: {
sessionID: string
requestID: string
@@ -6136,71 +5631,71 @@ export type V2EventQuestionV2Replied = {
}
}
export type V2EventQuestionV2Rejected = {
export type QuestionV2Rejected = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.v2.rejected"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "question.v2.rejected"
data: {
sessionID: string
requestID: string
}
}
export type V2EventTodoUpdated = {
export type TodoUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "todo.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "todo.updated"
data: {
sessionID: string
todos: Array<Todo>
}
}
export type V2EventLspUpdated = {
export type LspUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "lsp.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "lsp.updated"
data: {
[key: string]: unknown
}
}
export type V2EventPermissionAsked = {
export type PermissionAsked = {
id: string
metadata?: {
[key: string]: unknown
}
type: "permission.asked"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "permission.asked"
data: {
id: string
sessionID: string
@@ -6217,18 +5712,18 @@ export type V2EventPermissionAsked = {
}
}
export type V2EventPermissionReplied = {
export type PermissionReplied = {
id: string
metadata?: {
[key: string]: unknown
}
type: "permission.replied"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "permission.replied"
data: {
sessionID: string
requestID: string
@@ -6236,35 +5731,35 @@ export type V2EventPermissionReplied = {
}
}
export type V2EventTuiPromptAppend = {
export type TuiPromptAppend = {
id: string
metadata?: {
[key: string]: unknown
}
type: "tui.prompt.append"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "tui.prompt.append"
data: {
text: string
}
}
export type V2EventTuiCommandExecute = {
export type TuiCommandExecute = {
id: string
metadata?: {
[key: string]: unknown
}
type: "tui.command.execute"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "tui.command.execute"
data: {
command:
| "session.list"
@@ -6287,18 +5782,18 @@ export type V2EventTuiCommandExecute = {
}
}
export type V2EventTuiToastShow = {
export type TuiToastShow = {
id: string
metadata?: {
[key: string]: unknown
}
type: "tui.toast.show"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "tui.toast.show"
data: {
title?: string
message: string
@@ -6307,18 +5802,18 @@ export type V2EventTuiToastShow = {
}
}
export type V2EventTuiSessionSelect = {
export type TuiSessionSelect = {
id: string
metadata?: {
[key: string]: unknown
}
type: "tui.session.select"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "tui.session.select"
data: {
/**
* Session ID to navigate to
@@ -6327,53 +5822,53 @@ export type V2EventTuiSessionSelect = {
}
}
export type V2EventMcpToolsChanged = {
export type McpToolsChanged = {
id: string
metadata?: {
[key: string]: unknown
}
type: "mcp.tools.changed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "mcp.tools.changed"
data: {
server: string
}
}
export type V2EventMcpBrowserOpenFailed = {
export type McpBrowserOpenFailed = {
id: string
metadata?: {
[key: string]: unknown
}
type: "mcp.browser.open.failed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "mcp.browser.open.failed"
data: {
mcpName: string
url: string
}
}
export type V2EventCommandExecuted = {
export type CommandExecuted = {
id: string
metadata?: {
[key: string]: unknown
}
type: "command.executed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "command.executed"
data: {
name: string
sessionID: string
@@ -6382,18 +5877,18 @@ export type V2EventCommandExecuted = {
}
}
export type V2EventProjectUpdated = {
export type ProjectUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "project.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "project.updated"
data: {
id: string
worktree: string
@@ -6406,53 +5901,35 @@ export type V2EventProjectUpdated = {
}
}
export type V2EventSessionStatus = {
export type SessionIdle = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.status"
data: {
sessionID: string
status: SessionStatus
}
}
export type V2EventSessionIdle = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.idle"
data: {
sessionID: string
}
}
export type V2EventQuestionAsked = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
}
}
export type QuestionAsked = {
id: string
metadata?: {
[key: string]: unknown
}
type: "question.asked"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
id: string
sessionID: string
@@ -6464,193 +5941,156 @@ export type V2EventQuestionAsked = {
}
}
export type V2EventQuestionReplied = {
export type SessionCompacted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "question.replied"
data: {
sessionID: string
requestID: string
answers: Array<QuestionAnswer>
}
}
export type V2EventQuestionRejected = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "question.rejected"
data: {
sessionID: string
requestID: string
}
}
export type V2EventSessionCompacted = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.compacted"
data: {
sessionID: string
}
}
export type V2EventVcsBranchUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
sessionID: string
}
}
export type VcsBranchUpdated = {
id: string
metadata?: {
[key: string]: unknown
}
type: "vcs.branch.updated"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
data: {
branch?: string
}
}
export type V2EventWorkspaceReady = {
export type WorkspaceReady = {
id: string
metadata?: {
[key: string]: unknown
}
type: "workspace.ready"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "workspace.ready"
data: {
name: string
}
}
export type V2EventWorkspaceFailed = {
export type WorkspaceFailed = {
id: string
metadata?: {
[key: string]: unknown
}
type: "workspace.failed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "workspace.failed"
data: {
message: string
}
}
export type V2EventWorkspaceStatus = {
export type WorkspaceStatus = {
id: string
metadata?: {
[key: string]: unknown
}
type: "workspace.status"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "workspace.status"
data: {
workspaceID: string
status: "connected" | "connecting" | "disconnected" | "error"
}
}
export type V2EventWorktreeReady = {
export type WorktreeReady = {
id: string
metadata?: {
[key: string]: unknown
}
type: "worktree.ready"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "worktree.ready"
data: {
name: string
branch?: string
}
}
export type V2EventWorktreeFailed = {
export type WorktreeFailed = {
id: string
metadata?: {
[key: string]: unknown
}
type: "worktree.failed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "worktree.failed"
data: {
message: string
}
}
export type V2EventServerConnected = {
export type ServerConnected = {
id: string
metadata?: {
[key: string]: unknown
}
type: "server.connected"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "server.connected"
data: {
[key: string]: unknown
}
}
export type V2EventGlobalDisposed = {
export type GlobalDisposed = {
id: string
metadata?: {
[key: string]: unknown
}
type: "global.disposed"
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "global.disposed"
data: {
[key: string]: unknown
}
@@ -12384,6 +11824,44 @@ export type V2SessionContextResponses = {
export type V2SessionContextResponse = V2SessionContextResponses[keyof V2SessionContextResponses]
export type V2SessionHistoryData = {
body?: never
path: {
sessionID: string
}
query?: {
limit?: number
after?: number
}
url: "/api/session/{sessionID}/history"
}
export type V2SessionHistoryErrors = {
/**
* InvalidRequestError
*/
400: InvalidRequestError
/**
* UnauthorizedError
*/
401: UnauthorizedError
/**
* SessionNotFoundError
*/
404: SessionNotFoundError
}
export type V2SessionHistoryError = V2SessionHistoryErrors[keyof V2SessionHistoryErrors]
export type V2SessionHistoryResponses = {
/**
* SessionHistory
*/
200: SessionHistory
}
export type V2SessionHistoryResponse = V2SessionHistoryResponses[keyof V2SessionHistoryResponses]
export type V2SessionEventsData = {
body?: never
path: {
@@ -12419,7 +11897,7 @@ export type V2SessionEventsResponses = {
200: {
id: string
event: string
data: string
data: SessionDurableEvent1
}
}
@@ -0,0 +1,12 @@
import { expect, test } from "bun:test"
import type { V2SessionHistoryData } from "../src/v2/gen/types.gen"
test("uses numeric Session history positions", () => {
const input = {
path: { sessionID: "ses_test" },
query: { after: 1, limit: 50 },
url: "/api/session/{sessionID}/history",
} satisfies V2SessionHistoryData
expect(input.query.after).toBe(1)
})
+1344 -3852
View File
@@ -11130,6 +11130,97 @@
]
}
},
"/api/session/{sessionID}/history": {
"get": {
"tags": ["sessions"],
"operationId": "v2.session.history",
"parameters": [
{
"name": "sessionID",
"in": "path",
"schema": {
"type": "string",
"pattern": "^ses"
},
"required": true
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "string"
},
"required": false
},
{
"name": "after",
"in": "query",
"schema": {
"type": "string"
},
"required": false
}
],
"security": [],
"responses": {
"200": {
"description": "SessionHistory",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SessionHistory"
}
}
}
},
"400": {
"description": "InvalidRequestError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidRequestError"
}
}
}
},
"401": {
"description": "UnauthorizedError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedError"
}
}
}
},
"404": {
"description": "SessionNotFoundError",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/SessionNotFoundError"
},
{
"$ref": "#/components/schemas/SessionNotFoundError"
}
]
}
}
}
}
},
"description": "Read one finite page of public durable Session events after an exclusive aggregate sequence. Newly committed events may appear on later pages.",
"summary": "Get session history",
"x-codeSamples": [
{
"lang": "js",
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.history({\n ...\n})"
}
]
}
},
"/api/session/{sessionID}/event": {
"get": {
"tags": ["sessions"],
@@ -11169,96 +11260,7 @@
"type": "string"
},
"data": {
"type": "string",
"contentSchema": {
"oneOf": [
{
"$ref": "#/components/schemas/SessionNextAgentSwitched"
},
{
"$ref": "#/components/schemas/SessionNextModelSwitched"
},
{
"$ref": "#/components/schemas/SessionNextMoved"
},
{
"$ref": "#/components/schemas/SessionNextPrompted"
},
{
"$ref": "#/components/schemas/SessionNextPromptAdmitted"
},
{
"$ref": "#/components/schemas/SessionNextContextUpdated"
},
{
"$ref": "#/components/schemas/SessionNextSynthetic"
},
{
"$ref": "#/components/schemas/SessionNextShellStarted"
},
{
"$ref": "#/components/schemas/SessionNextShellEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepStarted"
},
{
"$ref": "#/components/schemas/SessionNextStepEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepFailed"
},
{
"$ref": "#/components/schemas/SessionNextTextStarted"
},
{
"$ref": "#/components/schemas/SessionNextTextEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolInputStarted"
},
{
"$ref": "#/components/schemas/SessionNextToolInputEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolCalled"
},
{
"$ref": "#/components/schemas/SessionNextToolProgress"
},
{
"$ref": "#/components/schemas/SessionNextToolSuccess"
},
{
"$ref": "#/components/schemas/SessionNextToolFailed"
},
{
"$ref": "#/components/schemas/SessionNextReasoningStarted"
},
{
"$ref": "#/components/schemas/SessionNextReasoningEnded"
},
{
"$ref": "#/components/schemas/SessionNextRetried"
},
{
"$ref": "#/components/schemas/SessionNextCompactionStarted"
},
{
"$ref": "#/components/schemas/SessionNextCompactionEnded"
},
{
"$ref": "#/components/schemas/SessionNextRevertStaged"
},
{
"$ref": "#/components/schemas/SessionNextRevertCleared"
},
{
"$ref": "#/components/schemas/SessionNextRevertCommitted"
}
]
},
"contentMediaType": "application/json"
"$ref": "#/components/schemas/SessionDurableEvent1"
}
},
"required": ["id", "event", "data"],
@@ -23626,6 +23628,117 @@
"required": ["_tag", "message"],
"additionalProperties": false
},
"SessionDurableEvent": {
"oneOf": [
{
"$ref": "#/components/schemas/SessionNextAgentSwitched"
},
{
"$ref": "#/components/schemas/SessionNextModelSwitched"
},
{
"$ref": "#/components/schemas/SessionNextMoved"
},
{
"$ref": "#/components/schemas/SessionNextPrompted"
},
{
"$ref": "#/components/schemas/SessionNextPromptAdmitted"
},
{
"$ref": "#/components/schemas/SessionNextContextUpdated"
},
{
"$ref": "#/components/schemas/SessionNextSynthetic"
},
{
"$ref": "#/components/schemas/SessionNextShellStarted"
},
{
"$ref": "#/components/schemas/SessionNextShellEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepStarted"
},
{
"$ref": "#/components/schemas/SessionNextStepEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepFailed"
},
{
"$ref": "#/components/schemas/SessionNextTextStarted"
},
{
"$ref": "#/components/schemas/SessionNextTextEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolInputStarted"
},
{
"$ref": "#/components/schemas/SessionNextToolInputEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolCalled"
},
{
"$ref": "#/components/schemas/SessionNextToolProgress"
},
{
"$ref": "#/components/schemas/SessionNextToolSuccess"
},
{
"$ref": "#/components/schemas/SessionNextToolFailed"
},
{
"$ref": "#/components/schemas/SessionNextReasoningStarted"
},
{
"$ref": "#/components/schemas/SessionNextReasoningEnded"
},
{
"$ref": "#/components/schemas/SessionNextRetried"
},
{
"$ref": "#/components/schemas/SessionNextCompactionStarted"
},
{
"$ref": "#/components/schemas/SessionNextCompactionEnded"
},
{
"$ref": "#/components/schemas/SessionNextRevertStaged"
},
{
"$ref": "#/components/schemas/SessionNextRevertCleared"
},
{
"$ref": "#/components/schemas/SessionNextRevertCommitted"
}
]
},
"SessionHistory": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SessionDurableEvent"
}
},
"hasMore": {
"type": "boolean"
}
},
"required": ["data", "hasMore"],
"additionalProperties": false
},
"SessionDurableEvent1": {
"type": "string",
"contentSchema": {
"$ref": "#/components/schemas/SessionDurableEvent"
},
"contentMediaType": "application/json"
},
"SessionMessagesResponse": {
"type": "object",
"properties": {
@@ -23668,274 +23781,475 @@
"required": ["_tag", "providerID", "message"],
"additionalProperties": false
},
"V2Event": {
"OutputFormat1": {
"anyOf": [
{
"$ref": "#/components/schemas/V2EventModels-devRefreshed"
},
{
"$ref": "#/components/schemas/V2EventIntegrationUpdated"
},
{
"$ref": "#/components/schemas/V2EventIntegrationConnectionUpdated"
},
{
"$ref": "#/components/schemas/V2EventCatalogUpdated"
},
{
"$ref": "#/components/schemas/V2EventSessionCreated"
},
{
"$ref": "#/components/schemas/V2EventSessionUpdated"
},
{
"$ref": "#/components/schemas/V2EventSessionDeleted"
},
{
"$ref": "#/components/schemas/V2EventMessageUpdated"
},
{
"$ref": "#/components/schemas/V2EventMessageRemoved"
},
{
"$ref": "#/components/schemas/V2EventMessagePartUpdated"
},
{
"$ref": "#/components/schemas/V2EventMessagePartRemoved"
},
{
"$ref": "#/components/schemas/V2EventSessionNextAgentSwitched"
},
{
"$ref": "#/components/schemas/V2EventSessionNextModelSwitched"
},
{
"$ref": "#/components/schemas/V2EventSessionNextMoved"
},
{
"$ref": "#/components/schemas/V2EventSessionNextPrompted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextPromptAdmitted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextContextUpdated"
},
{
"$ref": "#/components/schemas/V2EventSessionNextSynthetic"
},
{
"$ref": "#/components/schemas/V2EventSessionNextShellStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextShellEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextStepStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextStepEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextStepFailed"
},
{
"$ref": "#/components/schemas/V2EventSessionNextTextStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextTextDelta"
},
{
"$ref": "#/components/schemas/V2EventSessionNextTextEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextReasoningStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextReasoningDelta"
},
{
"$ref": "#/components/schemas/V2EventSessionNextReasoningEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolInputStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolInputDelta"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolInputEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolCalled"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolProgress"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolSuccess"
},
{
"$ref": "#/components/schemas/V2EventSessionNextToolFailed"
},
{
"$ref": "#/components/schemas/V2EventSessionNextRetried"
},
{
"$ref": "#/components/schemas/V2EventSessionNextCompactionStarted"
},
{
"$ref": "#/components/schemas/V2EventSessionNextCompactionDelta"
},
{
"$ref": "#/components/schemas/V2EventSessionNextCompactionEnded"
},
{
"$ref": "#/components/schemas/V2EventSessionNextRevertStaged"
},
{
"$ref": "#/components/schemas/V2EventSessionNextRevertCleared"
},
{
"$ref": "#/components/schemas/V2EventSessionNextRevertCommitted"
},
{
"$ref": "#/components/schemas/V2EventMessagePartDelta"
},
{
"$ref": "#/components/schemas/V2EventSessionDiff"
},
{
"$ref": "#/components/schemas/V2EventSessionError"
},
{
"$ref": "#/components/schemas/V2EventInstallationUpdated"
},
{
"$ref": "#/components/schemas/V2EventInstallationUpdate-available"
},
{
"$ref": "#/components/schemas/V2EventFileEdited"
},
{
"$ref": "#/components/schemas/V2EventReferenceUpdated"
},
{
"$ref": "#/components/schemas/V2EventPermissionV2Asked"
},
{
"$ref": "#/components/schemas/V2EventPermissionV2Replied"
},
{
"$ref": "#/components/schemas/V2EventPluginAdded"
},
{
"$ref": "#/components/schemas/V2EventProjectDirectoriesUpdated"
},
{
"$ref": "#/components/schemas/V2EventFileWatcherUpdated"
},
{
"$ref": "#/components/schemas/V2EventPtyCreated"
},
{
"$ref": "#/components/schemas/V2EventPtyUpdated"
},
{
"$ref": "#/components/schemas/V2EventPtyExited"
},
{
"$ref": "#/components/schemas/V2EventPtyDeleted"
},
{
"$ref": "#/components/schemas/V2EventQuestionV2Asked"
},
{
"$ref": "#/components/schemas/V2EventQuestionV2Replied"
},
{
"$ref": "#/components/schemas/V2EventQuestionV2Rejected"
},
{
"$ref": "#/components/schemas/V2EventTodoUpdated"
},
{
"$ref": "#/components/schemas/V2EventLspUpdated"
},
{
"$ref": "#/components/schemas/V2EventPermissionAsked"
},
{
"$ref": "#/components/schemas/V2EventPermissionReplied"
},
{
"$ref": "#/components/schemas/V2EventTuiPromptAppend"
},
{
"$ref": "#/components/schemas/V2EventTuiCommandExecute"
},
{
"$ref": "#/components/schemas/V2EventTuiToastShow"
},
{
"$ref": "#/components/schemas/V2EventTuiSessionSelect"
},
{
"$ref": "#/components/schemas/V2EventMcpToolsChanged"
},
{
"$ref": "#/components/schemas/V2EventMcpBrowserOpenFailed"
},
{
"$ref": "#/components/schemas/V2EventCommandExecuted"
},
{
"$ref": "#/components/schemas/V2EventProjectUpdated"
},
{
"$ref": "#/components/schemas/V2EventSessionStatus"
},
{
"$ref": "#/components/schemas/V2EventSessionIdle"
},
{
"$ref": "#/components/schemas/V2EventQuestionAsked"
},
{
"$ref": "#/components/schemas/V2EventQuestionReplied"
},
{
"$ref": "#/components/schemas/V2EventQuestionRejected"
},
{
"$ref": "#/components/schemas/V2EventSessionCompacted"
},
{
"$ref": "#/components/schemas/V2EventVcsBranchUpdated"
},
{
"$ref": "#/components/schemas/V2EventWorkspaceReady"
},
{
"$ref": "#/components/schemas/V2EventWorkspaceFailed"
},
{
"$ref": "#/components/schemas/V2EventWorkspaceStatus"
},
{
"$ref": "#/components/schemas/V2EventWorktreeReady"
},
{
"$ref": "#/components/schemas/V2EventWorktreeFailed"
},
{
"$ref": "#/components/schemas/V2EventServerConnected"
},
{
"$ref": "#/components/schemas/V2EventGlobalDisposed"
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["text"]
}
},
"required": ["type"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["json_schema"]
},
"schema": {
"$ref": "#/components/schemas/JSONSchema"
},
"retryCount": {
"type": "integer",
"minimum": 0
}
},
"required": ["type", "schema"],
"additionalProperties": false
}
]
},
"session.status": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["session.status"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"status": {
"$ref": "#/components/schemas/SessionStatus"
}
},
"required": ["sessionID", "status"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"question.replied": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.replied"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"requestID": {
"type": "string",
"pattern": "^que"
},
"answers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QuestionAnswer"
}
}
},
"required": ["sessionID", "requestID", "answers"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"question.rejected": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.rejected"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"requestID": {
"type": "string",
"pattern": "^que"
}
},
"required": ["sessionID", "requestID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2Event1": {
"anyOf": [
{
"$ref": "#/components/schemas/Models-devRefreshed"
},
{
"$ref": "#/components/schemas/IntegrationUpdated"
},
{
"$ref": "#/components/schemas/IntegrationConnectionUpdated"
},
{
"$ref": "#/components/schemas/CatalogUpdated"
},
{
"$ref": "#/components/schemas/SessionCreated"
},
{
"$ref": "#/components/schemas/SessionUpdated"
},
{
"$ref": "#/components/schemas/SessionDeleted"
},
{
"$ref": "#/components/schemas/MessageUpdated"
},
{
"$ref": "#/components/schemas/MessageRemoved"
},
{
"$ref": "#/components/schemas/MessagePartUpdated"
},
{
"$ref": "#/components/schemas/MessagePartRemoved"
},
{
"$ref": "#/components/schemas/SessionNextAgentSwitched"
},
{
"$ref": "#/components/schemas/SessionNextModelSwitched"
},
{
"$ref": "#/components/schemas/SessionNextMoved"
},
{
"$ref": "#/components/schemas/SessionNextPrompted"
},
{
"$ref": "#/components/schemas/SessionNextPromptAdmitted"
},
{
"$ref": "#/components/schemas/SessionNextContextUpdated"
},
{
"$ref": "#/components/schemas/SessionNextSynthetic"
},
{
"$ref": "#/components/schemas/SessionNextShellStarted"
},
{
"$ref": "#/components/schemas/SessionNextShellEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepStarted"
},
{
"$ref": "#/components/schemas/SessionNextStepEnded"
},
{
"$ref": "#/components/schemas/SessionNextStepFailed"
},
{
"$ref": "#/components/schemas/SessionNextTextStarted"
},
{
"$ref": "#/components/schemas/SessionNextTextDelta"
},
{
"$ref": "#/components/schemas/SessionNextTextEnded"
},
{
"$ref": "#/components/schemas/SessionNextReasoningStarted"
},
{
"$ref": "#/components/schemas/SessionNextReasoningDelta"
},
{
"$ref": "#/components/schemas/SessionNextReasoningEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolInputStarted"
},
{
"$ref": "#/components/schemas/SessionNextToolInputDelta"
},
{
"$ref": "#/components/schemas/SessionNextToolInputEnded"
},
{
"$ref": "#/components/schemas/SessionNextToolCalled"
},
{
"$ref": "#/components/schemas/SessionNextToolProgress"
},
{
"$ref": "#/components/schemas/SessionNextToolSuccess"
},
{
"$ref": "#/components/schemas/SessionNextToolFailed"
},
{
"$ref": "#/components/schemas/SessionNextRetried"
},
{
"$ref": "#/components/schemas/SessionNextCompactionStarted"
},
{
"$ref": "#/components/schemas/SessionNextCompactionDelta"
},
{
"$ref": "#/components/schemas/SessionNextCompactionEnded"
},
{
"$ref": "#/components/schemas/SessionNextRevertStaged"
},
{
"$ref": "#/components/schemas/SessionNextRevertCleared"
},
{
"$ref": "#/components/schemas/SessionNextRevertCommitted"
},
{
"$ref": "#/components/schemas/MessagePartDelta"
},
{
"$ref": "#/components/schemas/SessionDiff"
},
{
"$ref": "#/components/schemas/SessionError"
},
{
"$ref": "#/components/schemas/InstallationUpdated"
},
{
"$ref": "#/components/schemas/InstallationUpdate-available"
},
{
"$ref": "#/components/schemas/FileEdited"
},
{
"$ref": "#/components/schemas/ReferenceUpdated"
},
{
"$ref": "#/components/schemas/PermissionV2Asked"
},
{
"$ref": "#/components/schemas/PermissionV2Replied"
},
{
"$ref": "#/components/schemas/PluginAdded"
},
{
"$ref": "#/components/schemas/ProjectDirectoriesUpdated"
},
{
"$ref": "#/components/schemas/FileWatcherUpdated"
},
{
"$ref": "#/components/schemas/PtyCreated"
},
{
"$ref": "#/components/schemas/PtyUpdated"
},
{
"$ref": "#/components/schemas/PtyExited"
},
{
"$ref": "#/components/schemas/PtyDeleted"
},
{
"$ref": "#/components/schemas/QuestionV2Asked"
},
{
"$ref": "#/components/schemas/QuestionV2Replied"
},
{
"$ref": "#/components/schemas/QuestionV2Rejected"
},
{
"$ref": "#/components/schemas/TodoUpdated"
},
{
"$ref": "#/components/schemas/LspUpdated"
},
{
"$ref": "#/components/schemas/PermissionAsked"
},
{
"$ref": "#/components/schemas/PermissionReplied"
},
{
"$ref": "#/components/schemas/TuiPromptAppend"
},
{
"$ref": "#/components/schemas/TuiCommandExecute"
},
{
"$ref": "#/components/schemas/TuiToastShow"
},
{
"$ref": "#/components/schemas/TuiSessionSelect"
},
{
"$ref": "#/components/schemas/McpToolsChanged"
},
{
"$ref": "#/components/schemas/McpBrowserOpenFailed"
},
{
"$ref": "#/components/schemas/CommandExecuted"
},
{
"$ref": "#/components/schemas/ProjectUpdated"
},
{
"$ref": "#/components/schemas/session.status"
},
{
"$ref": "#/components/schemas/SessionIdle"
},
{
"$ref": "#/components/schemas/QuestionAsked"
},
{
"$ref": "#/components/schemas/question.replied"
},
{
"$ref": "#/components/schemas/question.rejected"
},
{
"$ref": "#/components/schemas/SessionCompacted"
},
{
"$ref": "#/components/schemas/VcsBranchUpdated"
},
{
"$ref": "#/components/schemas/WorkspaceReady"
},
{
"$ref": "#/components/schemas/WorkspaceFailed"
},
{
"$ref": "#/components/schemas/WorkspaceStatus"
},
{
"$ref": "#/components/schemas/WorktreeReady"
},
{
"$ref": "#/components/schemas/WorktreeFailed"
},
{
"$ref": "#/components/schemas/ServerConnected"
},
{
"$ref": "#/components/schemas/GlobalDisposed"
}
]
},
"V2Event": {
"type": "string",
"contentSchema": {
"$ref": "#/components/schemas/V2Event1"
},
"contentMediaType": "application/json"
},
"ForbiddenError": {
"type": "object",
"properties": {
@@ -27643,42 +27957,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -27733,42 +28015,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -27823,42 +28073,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -27912,42 +28130,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28006,42 +28192,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28100,42 +28254,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28190,42 +28312,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28280,42 +28370,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28373,42 +28431,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28462,42 +28488,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28558,42 +28552,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28689,42 +28651,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28779,42 +28709,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28869,42 +28767,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -28962,42 +28828,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29055,42 +28889,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29148,42 +28950,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29257,42 +29027,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29356,42 +29094,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29475,42 +29181,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29582,42 +29256,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29675,42 +29317,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29771,42 +29381,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29860,42 +29438,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -29951,42 +29497,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -30048,42 +29562,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -30134,42 +29616,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -30217,42 +29667,10 @@
"type": "string"
},
"seq": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
},
"version": {
"anyOf": [
{
"type": "number"
},
{
"type": "string",
"enum": ["NaN"]
},
{
"type": "string",
"enum": ["Infinity"]
},
{
"type": "string",
"enum": ["-Infinity"]
}
]
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
@@ -31240,7 +30658,7 @@
"required": ["name", "location", "content"],
"additionalProperties": false
},
"V2EventModels-devRefreshed": {
"Models-devRefreshed": {
"type": "object",
"properties": {
"id": {
@@ -31250,47 +30668,10 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["models-dev.refreshed"]
},
"data": {
"type": "object",
"properties": {}
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventIntegrationUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -31310,28 +30691,28 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {}
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"IntegrationUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["integration.updated"]
},
"data": {
"type": "object",
"properties": {}
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventIntegrationConnectionUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -31351,10 +30732,47 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {}
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"IntegrationConnectionUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["integration.connection.updated"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -31369,7 +30787,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventCatalogUpdated": {
"CatalogUpdated": {
"type": "object",
"properties": {
"id": {
@@ -31379,6 +30797,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["catalog.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -31398,10 +30820,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["catalog.updated"]
},
"data": {
"type": "object",
"properties": {}
@@ -31410,7 +30828,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionCreated": {
"SessionCreated": {
"type": "object",
"properties": {
"id": {
@@ -31420,57 +30838,10 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.created"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -31490,38 +30861,38 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"SessionUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["session.updated"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionDeleted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -31541,38 +30912,38 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"SessionDeleted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["session.deleted"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMessageUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -31592,10 +30963,57 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"info": {
"$ref": "#/components/schemas/Session"
}
},
"required": ["sessionID", "info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"MessageUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["message.updated"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -31614,7 +31032,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMessageRemoved": {
"MessageRemoved": {
"type": "object",
"properties": {
"id": {
@@ -31624,6 +31042,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["message.removed"]
},
"durable": {
"type": "object",
"properties": {
@@ -31643,10 +31065,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["message.removed"]
},
"data": {
"type": "object",
"properties": {
@@ -31666,7 +31084,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMessagePartUpdated": {
"MessagePartUpdated": {
"type": "object",
"properties": {
"id": {
@@ -31676,6 +31094,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["message.part.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -31695,10 +31117,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["message.part.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -31720,7 +31138,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMessagePartRemoved": {
"MessagePartRemoved": {
"type": "object",
"properties": {
"id": {
@@ -31730,6 +31148,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["message.part.removed"]
},
"durable": {
"type": "object",
"properties": {
@@ -31749,10 +31171,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["message.part.removed"]
},
"data": {
"type": "object",
"properties": {
@@ -31776,7 +31194,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextAgentSwitched": {
"SessionNextTextDelta": {
"type": "object",
"properties": {
"id": {
@@ -31786,839 +31204,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.agent.switched"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"agent": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "messageID", "agent"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextModelSwitched": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.model.switched"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"model": {
"$ref": "#/components/schemas/ModelRef"
}
},
"required": ["timestamp", "sessionID", "messageID", "model"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextMoved": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.moved"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"subdirectory": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "location"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextPrompted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.prompted"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"prompt": {
"$ref": "#/components/schemas/Prompt"
},
"delivery": {
"type": "string",
"enum": ["steer", "queue"]
}
},
"required": ["timestamp", "sessionID", "messageID", "prompt", "delivery"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextPromptAdmitted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.prompt.admitted"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"prompt": {
"$ref": "#/components/schemas/Prompt"
},
"delivery": {
"type": "string",
"enum": ["steer", "queue"]
}
},
"required": ["timestamp", "sessionID", "messageID", "prompt", "delivery"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextContextUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.context.updated"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"text": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "messageID", "text"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextSynthetic": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.synthetic"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"text": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "messageID", "text"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextShellStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.shell.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"command": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "messageID", "callID", "command"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextShellEnded": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.shell.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"callID": {
"type": "string"
},
"output": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "callID", "output"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextStepStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.step.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"agent": {
"type": "string"
},
"model": {
"$ref": "#/components/schemas/ModelRef"
},
"snapshot": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "agent", "model"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextStepEnded": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.step.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"finish": {
"type": "string"
},
"cost": {
"type": "number"
},
"tokens": {
"type": "object",
"properties": {
"input": {
"type": "number"
},
"output": {
"type": "number"
},
"reasoning": {
"type": "number"
},
"cache": {
"type": "object",
"properties": {
"read": {
"type": "number"
},
"write": {
"type": "number"
}
},
"required": ["read", "write"],
"additionalProperties": false
}
},
"required": ["input", "output", "reasoning", "cache"],
"additionalProperties": false
},
"snapshot": {
"type": "string"
},
"files": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "finish", "cost", "tokens"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextStepFailed": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.step.failed"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"error": {
"$ref": "#/components/schemas/SessionErrorUnknown"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "error"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextTextStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.text.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"textID": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "textID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextTextDelta": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.text.delta"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -32647,7 +31255,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextTextEnded": {
"SessionNextReasoningDelta": {
"type": "object",
"properties": {
"id": {
@@ -32657,151 +31265,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.text.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"textID": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "textID", "text"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextReasoningStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.reasoning.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"reasoningID": {
"type": "string"
},
"providerMetadata": {
"$ref": "#/components/schemas/LLMProviderMetadata"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "reasoningID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextReasoningDelta": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.reasoning.delta"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -32830,7 +31316,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextReasoningEnded": {
"SessionNextToolInputDelta": {
"type": "object",
"properties": {
"id": {
@@ -32840,154 +31326,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.reasoning.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"reasoningID": {
"type": "string"
},
"text": {
"type": "string"
},
"providerMetadata": {
"$ref": "#/components/schemas/LLMProviderMetadata"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "reasoningID", "text"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolInputStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.input.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "name"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolInputDelta": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.input.delta"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -33016,7 +31377,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolInputEnded": {
"SessionNextCompactionDelta": {
"type": "object",
"properties": {
"id": {
@@ -33026,512 +31387,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.input.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"text": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "text"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolCalled": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.called"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"tool": {
"type": "string"
},
"input": {
"type": "object"
},
"provider": {
"type": "object",
"properties": {
"executed": {
"type": "boolean"
},
"metadata": {
"$ref": "#/components/schemas/LLMProviderMetadata"
}
},
"required": ["executed"],
"additionalProperties": false
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "tool", "input", "provider"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolProgress": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.progress"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"structured": {
"type": "object"
},
"content": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LLMToolContent"
}
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "structured", "content"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolSuccess": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.success"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"structured": {
"type": "object"
},
"content": {
"type": "array",
"items": {
"$ref": "#/components/schemas/LLMToolContent"
}
},
"outputPaths": {
"type": "array",
"items": {
"type": "string"
}
},
"result": {},
"provider": {
"type": "object",
"properties": {
"executed": {
"type": "boolean"
},
"metadata": {
"$ref": "#/components/schemas/LLMProviderMetadata"
}
},
"required": ["executed"],
"additionalProperties": false
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "structured", "content", "provider"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextToolFailed": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.tool.failed"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"assistantMessageID": {
"type": "string",
"pattern": "^msg_"
},
"callID": {
"type": "string"
},
"error": {
"$ref": "#/components/schemas/SessionErrorUnknown"
},
"result": {},
"provider": {
"type": "object",
"properties": {
"executed": {
"type": "boolean"
},
"metadata": {
"$ref": "#/components/schemas/LLMProviderMetadata"
}
},
"required": ["executed"],
"additionalProperties": false
}
},
"required": ["timestamp", "sessionID", "assistantMessageID", "callID", "error", "provider"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextRetried": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.retried"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"attempt": {
"type": "number"
},
"error": {
"$ref": "#/components/schemas/SessionNextRetry_error"
}
},
"required": ["timestamp", "sessionID", "attempt", "error"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextCompactionStarted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.compaction.started"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"reason": {
"type": "string",
"enum": ["auto", "manual"]
}
},
"required": ["timestamp", "sessionID", "messageID", "reason"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextCompactionDelta": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.compaction.delta"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -33557,7 +31435,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextCompactionEnded": {
"MessagePartDelta": {
"type": "object",
"properties": {
"id": {
@@ -33567,254 +31445,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.compaction.ended"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
},
"reason": {
"type": "string",
"enum": ["auto", "manual"]
},
"text": {
"type": "string"
},
"recent": {
"type": "string"
}
},
"required": ["timestamp", "sessionID", "messageID", "reason", "text", "recent"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextRevertStaged": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.revert.staged"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"revert": {
"$ref": "#/components/schemas/RevertState"
}
},
"required": ["timestamp", "sessionID", "revert"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextRevertCleared": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.revert.cleared"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
}
},
"required": ["timestamp", "sessionID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionNextRevertCommitted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.next.revert.committed"]
},
"data": {
"type": "object",
"properties": {
"timestamp": {
"type": "number"
},
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"messageID": {
"type": "string",
"pattern": "^msg_"
}
},
"required": ["timestamp", "sessionID", "messageID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMessagePartDelta": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["message.part.delta"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -33844,7 +31497,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionDiff": {
"SessionDiff": {
"type": "object",
"properties": {
"id": {
@@ -33854,6 +31507,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["session.diff"]
},
"durable": {
"type": "object",
"properties": {
@@ -33873,10 +31530,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.diff"]
},
"data": {
"type": "object",
"properties": {
@@ -33898,7 +31551,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionError": {
"SessionError": {
"type": "object",
"properties": {
"id": {
@@ -33908,6 +31561,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["session.error"]
},
"durable": {
"type": "object",
"properties": {
@@ -33927,10 +31584,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.error"]
},
"data": {
"type": "object",
"properties": {
@@ -33973,7 +31626,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventInstallationUpdated": {
"InstallationUpdated": {
"type": "object",
"properties": {
"id": {
@@ -33983,53 +31636,10 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["installation.updated"]
},
"data": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": ["version"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventInstallationUpdate-available": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -34049,34 +31659,34 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": ["version"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"InstallationUpdate-available": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["installation.update-available"]
},
"data": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": ["version"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventFileEdited": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -34096,10 +31706,53 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": ["version"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"FileEdited": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["file.edited"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -34114,7 +31767,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventReferenceUpdated": {
"ReferenceUpdated": {
"type": "object",
"properties": {
"id": {
@@ -34124,6 +31777,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["reference.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -34143,10 +31800,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["reference.updated"]
},
"data": {
"type": "object",
"properties": {}
@@ -34155,7 +31808,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPermissionV2Asked": {
"PermissionV2Asked": {
"type": "object",
"properties": {
"id": {
@@ -34165,6 +31818,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["permission.v2.asked"]
},
"durable": {
"type": "object",
"properties": {
@@ -34184,10 +31841,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["permission.v2.asked"]
},
"data": {
"type": "object",
"properties": {
@@ -34228,7 +31881,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPermissionV2Replied": {
"PermissionV2Replied": {
"type": "object",
"properties": {
"id": {
@@ -34238,6 +31891,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["permission.v2.replied"]
},
"durable": {
"type": "object",
"properties": {
@@ -34257,10 +31914,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["permission.v2.replied"]
},
"data": {
"type": "object",
"properties": {
@@ -34283,7 +31936,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPluginAdded": {
"PluginAdded": {
"type": "object",
"properties": {
"id": {
@@ -34293,6 +31946,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["plugin.added"]
},
"durable": {
"type": "object",
"properties": {
@@ -34312,10 +31969,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["plugin.added"]
},
"data": {
"type": "object",
"properties": {
@@ -34330,7 +31983,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventProjectDirectoriesUpdated": {
"ProjectDirectoriesUpdated": {
"type": "object",
"properties": {
"id": {
@@ -34340,6 +31993,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["project.directories.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -34359,10 +32016,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["project.directories.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -34377,7 +32030,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventFileWatcherUpdated": {
"FileWatcherUpdated": {
"type": "object",
"properties": {
"id": {
@@ -34387,6 +32040,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["file.watcher.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -34406,10 +32063,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["file.watcher.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -34428,7 +32081,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPtyCreated": {
"PtyCreated": {
"type": "object",
"properties": {
"id": {
@@ -34438,53 +32091,10 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["pty.created"]
},
"data": {
"type": "object",
"properties": {
"info": {
"$ref": "#/components/schemas/Pty"
}
},
"required": ["info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPtyUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -34504,34 +32114,34 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"info": {
"$ref": "#/components/schemas/Pty"
}
},
"required": ["info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"PtyUpdated": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["pty.updated"]
},
"data": {
"type": "object",
"properties": {
"info": {
"$ref": "#/components/schemas/Pty"
}
},
"required": ["info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPtyExited": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
@@ -34551,10 +32161,53 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
"info": {
"$ref": "#/components/schemas/Pty"
}
},
"required": ["info"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"PtyExited": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["pty.exited"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -34574,7 +32227,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPtyDeleted": {
"PtyDeleted": {
"type": "object",
"properties": {
"id": {
@@ -34584,6 +32237,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["pty.deleted"]
},
"durable": {
"type": "object",
"properties": {
@@ -34603,10 +32260,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["pty.deleted"]
},
"data": {
"type": "object",
"properties": {
@@ -34622,7 +32275,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionV2Asked": {
"QuestionV2Asked": {
"type": "object",
"properties": {
"id": {
@@ -34632,6 +32285,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.v2.asked"]
},
"durable": {
"type": "object",
"properties": {
@@ -34651,10 +32308,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.v2.asked"]
},
"data": {
"type": "object",
"properties": {
@@ -34684,7 +32337,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionV2Replied": {
"QuestionV2Replied": {
"type": "object",
"properties": {
"id": {
@@ -34694,6 +32347,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.v2.replied"]
},
"durable": {
"type": "object",
"properties": {
@@ -34713,10 +32370,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.v2.replied"]
},
"data": {
"type": "object",
"properties": {
@@ -34742,7 +32395,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionV2Rejected": {
"QuestionV2Rejected": {
"type": "object",
"properties": {
"id": {
@@ -34752,6 +32405,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.v2.rejected"]
},
"durable": {
"type": "object",
"properties": {
@@ -34771,10 +32428,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.v2.rejected"]
},
"data": {
"type": "object",
"properties": {
@@ -34794,7 +32447,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventTodoUpdated": {
"TodoUpdated": {
"type": "object",
"properties": {
"id": {
@@ -34804,6 +32457,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["todo.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -34823,10 +32480,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["todo.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -34848,7 +32501,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventLspUpdated": {
"LspUpdated": {
"type": "object",
"properties": {
"id": {
@@ -34858,6 +32511,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["lsp.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -34877,10 +32534,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["lsp.updated"]
},
"data": {
"type": "object",
"properties": {}
@@ -34889,7 +32542,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPermissionAsked": {
"PermissionAsked": {
"type": "object",
"properties": {
"id": {
@@ -34899,6 +32552,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["permission.asked"]
},
"durable": {
"type": "object",
"properties": {
@@ -34918,10 +32575,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["permission.asked"]
},
"data": {
"type": "object",
"properties": {
@@ -34972,7 +32625,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventPermissionReplied": {
"PermissionReplied": {
"type": "object",
"properties": {
"id": {
@@ -34982,6 +32635,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["permission.replied"]
},
"durable": {
"type": "object",
"properties": {
@@ -35001,10 +32658,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["permission.replied"]
},
"data": {
"type": "object",
"properties": {
@@ -35028,7 +32681,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventTuiPromptAppend": {
"TuiPromptAppend": {
"type": "object",
"properties": {
"id": {
@@ -35038,6 +32691,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["tui.prompt.append"]
},
"durable": {
"type": "object",
"properties": {
@@ -35057,10 +32714,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["tui.prompt.append"]
},
"data": {
"type": "object",
"properties": {
@@ -35075,7 +32728,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventTuiCommandExecute": {
"TuiCommandExecute": {
"type": "object",
"properties": {
"id": {
@@ -35085,6 +32738,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["tui.command.execute"]
},
"durable": {
"type": "object",
"properties": {
@@ -35104,10 +32761,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["tui.command.execute"]
},
"data": {
"type": "object",
"properties": {
@@ -35147,7 +32800,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventTuiToastShow": {
"TuiToastShow": {
"type": "object",
"properties": {
"id": {
@@ -35157,6 +32810,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["tui.toast.show"]
},
"durable": {
"type": "object",
"properties": {
@@ -35176,10 +32833,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["tui.toast.show"]
},
"data": {
"type": "object",
"properties": {
@@ -35205,7 +32858,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventTuiSessionSelect": {
"TuiSessionSelect": {
"type": "object",
"properties": {
"id": {
@@ -35215,6 +32868,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["tui.session.select"]
},
"durable": {
"type": "object",
"properties": {
@@ -35234,10 +32891,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["tui.session.select"]
},
"data": {
"type": "object",
"properties": {
@@ -35254,7 +32907,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMcpToolsChanged": {
"McpToolsChanged": {
"type": "object",
"properties": {
"id": {
@@ -35264,6 +32917,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["mcp.tools.changed"]
},
"durable": {
"type": "object",
"properties": {
@@ -35283,10 +32940,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["mcp.tools.changed"]
},
"data": {
"type": "object",
"properties": {
@@ -35301,7 +32954,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventMcpBrowserOpenFailed": {
"McpBrowserOpenFailed": {
"type": "object",
"properties": {
"id": {
@@ -35311,6 +32964,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["mcp.browser.open.failed"]
},
"durable": {
"type": "object",
"properties": {
@@ -35330,10 +32987,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["mcp.browser.open.failed"]
},
"data": {
"type": "object",
"properties": {
@@ -35351,7 +33004,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventCommandExecuted": {
"CommandExecuted": {
"type": "object",
"properties": {
"id": {
@@ -35361,6 +33014,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["command.executed"]
},
"durable": {
"type": "object",
"properties": {
@@ -35380,10 +33037,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["command.executed"]
},
"data": {
"type": "object",
"properties": {
@@ -35409,7 +33062,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventProjectUpdated": {
"ProjectUpdated": {
"type": "object",
"properties": {
"id": {
@@ -35419,6 +33072,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["project.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -35438,10 +33095,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["project.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -35480,7 +33133,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionStatus": {
"SessionIdle": {
"type": "object",
"properties": {
"id": {
@@ -35490,80 +33143,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.status"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"status": {
"$ref": "#/components/schemas/SessionStatus"
}
},
"required": ["sessionID", "status"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionIdle": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.idle"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -35579,7 +33181,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionAsked": {
"QuestionAsked": {
"type": "object",
"properties": {
"id": {
@@ -35589,6 +33191,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["question.asked"]
},
"durable": {
"type": "object",
"properties": {
@@ -35608,10 +33214,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.asked"]
},
"data": {
"type": "object",
"properties": {
@@ -35641,7 +33243,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionReplied": {
"SessionCompacted": {
"type": "object",
"properties": {
"id": {
@@ -35651,139 +33253,29 @@
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.replied"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"requestID": {
"type": "string",
"pattern": "^que"
},
"answers": {
"type": "array",
"items": {
"$ref": "#/components/schemas/QuestionAnswer"
}
}
},
"required": ["sessionID", "requestID", "answers"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventQuestionRejected": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["question.rejected"]
},
"data": {
"type": "object",
"properties": {
"sessionID": {
"type": "string",
"pattern": "^ses"
},
"requestID": {
"type": "string",
"pattern": "^que"
}
},
"required": ["sessionID", "requestID"],
"additionalProperties": false
}
},
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventSessionCompacted": {
"type": "object",
"properties": {
"id": {
"type": "string",
"pattern": "^evt_"
},
"metadata": {
"type": "object"
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["session.compacted"]
},
"durable": {
"type": "object",
"properties": {
"aggregateID": {
"type": "string"
},
"seq": {
"type": "integer"
},
"version": {
"type": "integer"
}
},
"required": ["aggregateID", "seq", "version"],
"additionalProperties": false
},
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"data": {
"type": "object",
"properties": {
@@ -35799,7 +33291,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventVcsBranchUpdated": {
"VcsBranchUpdated": {
"type": "object",
"properties": {
"id": {
@@ -35809,6 +33301,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["vcs.branch.updated"]
},
"durable": {
"type": "object",
"properties": {
@@ -35828,10 +33324,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["vcs.branch.updated"]
},
"data": {
"type": "object",
"properties": {
@@ -35845,7 +33337,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventWorkspaceReady": {
"WorkspaceReady": {
"type": "object",
"properties": {
"id": {
@@ -35855,6 +33347,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["workspace.ready"]
},
"durable": {
"type": "object",
"properties": {
@@ -35874,10 +33370,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["workspace.ready"]
},
"data": {
"type": "object",
"properties": {
@@ -35892,7 +33384,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventWorkspaceFailed": {
"WorkspaceFailed": {
"type": "object",
"properties": {
"id": {
@@ -35902,6 +33394,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["workspace.failed"]
},
"durable": {
"type": "object",
"properties": {
@@ -35921,10 +33417,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["workspace.failed"]
},
"data": {
"type": "object",
"properties": {
@@ -35939,7 +33431,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventWorkspaceStatus": {
"WorkspaceStatus": {
"type": "object",
"properties": {
"id": {
@@ -35949,6 +33441,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["workspace.status"]
},
"durable": {
"type": "object",
"properties": {
@@ -35968,10 +33464,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["workspace.status"]
},
"data": {
"type": "object",
"properties": {
@@ -35991,7 +33483,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventWorktreeReady": {
"WorktreeReady": {
"type": "object",
"properties": {
"id": {
@@ -36001,6 +33493,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["worktree.ready"]
},
"durable": {
"type": "object",
"properties": {
@@ -36020,10 +33516,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["worktree.ready"]
},
"data": {
"type": "object",
"properties": {
@@ -36041,7 +33533,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventWorktreeFailed": {
"WorktreeFailed": {
"type": "object",
"properties": {
"id": {
@@ -36051,6 +33543,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["worktree.failed"]
},
"durable": {
"type": "object",
"properties": {
@@ -36070,10 +33566,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["worktree.failed"]
},
"data": {
"type": "object",
"properties": {
@@ -36088,7 +33580,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventServerConnected": {
"ServerConnected": {
"type": "object",
"properties": {
"id": {
@@ -36098,6 +33590,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["server.connected"]
},
"durable": {
"type": "object",
"properties": {
@@ -36117,10 +33613,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["server.connected"]
},
"data": {
"type": "object",
"properties": {}
@@ -36129,7 +33621,7 @@
"required": ["id", "type", "data"],
"additionalProperties": false
},
"V2EventGlobalDisposed": {
"GlobalDisposed": {
"type": "object",
"properties": {
"id": {
@@ -36139,6 +33631,10 @@
"metadata": {
"type": "object"
},
"type": {
"type": "string",
"enum": ["global.disposed"]
},
"durable": {
"type": "object",
"properties": {
@@ -36158,10 +33654,6 @@
"location": {
"$ref": "#/components/schemas/LocationRef"
},
"type": {
"type": "string",
"enum": ["global.disposed"]
},
"data": {
"type": "object",
"properties": {}
+14 -8
View File
@@ -1,16 +1,19 @@
import { EventV2 } from "@opencode-ai/core/event"
import { Effect, Stream } from "effect"
import { OpenCodeEvent } from "@opencode-ai/protocol/groups/event"
import { Effect, Schema, Stream } from "effect"
import { HttpServerResponse } from "effect/unstable/http"
import { HttpApiBuilder } from "effect/unstable/httpapi"
import * as Sse from "effect/unstable/encoding/Sse"
import { Api } from "../api"
const subscriberCapacity = 256
function eventData(data: unknown): Sse.Event {
return {
_tag: "Event",
event: "message",
id: undefined,
data: JSON.stringify(data),
data: JSON.stringify(Schema.encodeUnknownSync(OpenCodeEvent)(data)),
}
}
@@ -24,13 +27,16 @@ export const EventHandler = HttpApiBuilder.group(Api, "server.event", (handlers)
type: "server.connected",
data: {},
}
const output = Stream.unwrap(
Effect.gen(function* () {
// Acquiring the bounded stream installs its listener before readiness is observable.
const live = yield* EventV2.allBounded(events, subscriberCapacity)
return Stream.make(connected).pipe(Stream.concat(live))
}),
).pipe(Stream.map(eventData), Stream.pipeThroughChannel(Sse.encode()))
const heartbeat = Stream.tick("15 seconds").pipe(Stream.map(() => ": heartbeat\n\n"))
return HttpServerResponse.stream(
Stream.make(connected).pipe(
Stream.concat(events.all()),
Stream.map(eventData),
Stream.pipeThroughChannel(Sse.encode()),
Stream.encodeText,
),
output.pipe(Stream.merge(heartbeat, { haltStrategy: "left" }), Stream.encodeText),
{
contentType: "text/event-stream",
headers: {
+26
View File
@@ -14,6 +14,7 @@ import {
import { AbsolutePath } from "@opencode-ai/core/schema"
const DefaultSessionsLimit = 50
const DefaultSessionHistoryLimit = 50
export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) =>
Effect.gen(function* () {
@@ -328,6 +329,31 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
}
}),
)
.handle(
"session.history",
Effect.fn(function* (ctx) {
return yield* session
.history({
sessionID: ctx.params.sessionID,
after: ctx.query.after,
limit: ctx.query.limit ?? DefaultSessionHistoryLimit,
})
.pipe(
Effect.map((page) => ({
data: page.events,
hasMore: page.hasMore,
})),
Effect.catchTag(
"Session.NotFoundError",
(error) =>
new SessionNotFoundError({
sessionID: error.sessionID,
message: `Session not found: ${error.sessionID}`,
}),
),
)
}),
)
.handle(
"session.events",
Effect.fn((ctx) =>
@@ -1,6 +1,7 @@
export function inlineCodeKind(text: string): "path" | "url" | undefined {
if (/^https?:\/\//i.test(text)) return "url"
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(text)) return
if (text === "/") return
if (/^\/[a-z][a-z0-9-]*$/i.test(text)) return
if (/\s/.test(text)) return
if (/[()\[\]{}*+=<>|&^"';]/.test(text)) return
+3 -3
View File
@@ -16,7 +16,7 @@ import type {
SessionMessageAssistantTool,
SessionV2Info,
SkillV2Info,
V2Event,
V2Event1,
} from "@opencode-ai/sdk/v2"
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "./helper"
@@ -121,7 +121,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
}
function handleEvent(event: V2Event) {
function handleEvent(event: V2Event1) {
switch (event.type) {
case "catalog.updated":
void Promise.all([
@@ -408,7 +408,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
...event,
data: event.properties,
location: { directory: metadata.directory, workspaceID: metadata.workspace },
} as V2Event)
} as V2Event1)
})
onCleanup(unsub)
})
+2 -1
View File
@@ -64,15 +64,16 @@
"generate:v2-oc2": "bun run script/build-oc2-v2-overrides.ts"
},
"devDependencies": {
"@solidjs/meta": "catalog:",
"@tailwindcss/vite": "catalog:",
"@tsconfig/node22": "catalog:",
"@types/bun": "catalog:",
"@types/katex": "0.16.7",
"@types/luxon": "catalog:",
"@typescript/native-preview": "catalog:",
"@solidjs/meta": "catalog:",
"solid-js": "catalog:",
"tailwindcss": "catalog:",
"tw-animate-css": "1.4.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-icons-spritesheet": "3.0.1",
+7
View File
@@ -1,5 +1,12 @@
# V2 Schema Changelog
## 2026-06-26: Add Finite Session History
- Add `GET /api/session/:sessionID/history` and generated Promise, Effect, and legacy JavaScript client methods.
- Page public durable Session events after an optional exclusive aggregate sequence, with an explicit `hasMore` exhaustion signal.
- Keep aggregate gaps legal, cap pages at 100 events, and preserve the existing durable replay-and-tail `sessions.events()` stream unchanged.
- Add no migration or durable-event version; this is a finite read API over the existing event manifest.
## 2026-06-22: Simplify Session Input Promotion
- Keep `session.next.prompt.admitted.1` as the durable, client-visible record of pending Session input.
+5 -1
View File
@@ -176,6 +176,10 @@ The synchronized `session.next.*` event family and projected Session-message mod
The first `sessions.events(...)` contract is durable-only during both replay and live tailing. This keeps one cursor equal to one persisted aggregate sequence and is sufficient for reconnect-safe consumers. A later UI-facing API may optionally interleave live-only deltas while connected, but those fragments must remain explicitly ephemeral: they cannot advance the durable cursor, replay after reconnect, or be mistaken for publication boundaries.
`sessions.history({ sessionID, after?, limit? })` is the finite counterpart for request/response consumers. `after` is an exclusive aggregate sequence, and omission starts before sequence zero. The response is `{ data, hasMore }`; callers derive the next `after` from the final event's durable sequence when `hasMore` is true. Public durable Session events are selected before pagination, which permits gaps from private or historical aggregate events while preserving strictly increasing unique sequences. The log has a moving head, so events committed between pages may appear on the next page.
The finite endpoint is `GET /api/session/:sessionID/history`, uses the normal Session Location and authorization middleware, defaults to 50 events, and accepts at most 100. It returns only events in the public durable Session schema. The existing `sessions.events()` replay-and-tail stream is unchanged.
Durable event tail wakeups are advisory and edge-triggered. Each active tail owns one sliding-capacity-1 dirty signal for its aggregate and re-queries SQLite after a wake. Repeated commits coalesce while the tail is busy because durable rows, not in-memory notifications, preserve every event and sequence. Subscribe and register the dirty signal before historical replay, then remove it when the tail closes, so replay handoff cannot miss a commit and inactive aggregates retain no wake state.
Event replay owner claims are separate from clustered Session execution ownership. The former already fences synchronized projection reconstruction; the latter still needs distributed active-run acquisition, stale-runtime rejection, interruption, and placement orchestration.
@@ -206,7 +210,7 @@ The first V2 `apply_patch` leaf supports add, update, and delete hunks. It parse
- Keep eager structured local-tool settlement: durably record each complete call, start its child execution immediately, await all started settlements after provider-turn consumption, persist every result, and reload history once before continuation.
- Buffer or coalesce streamed deltas before rewriting growing assistant projections.
- Revisit additional covering indexes as larger-history query shapes become concrete.
- Expose replayable Session events over HTTP and the generated SDK where remote consumers need them, deciding whether that public cursor should be opaque rather than the embedded API's branded aggregate sequence.
- Design any global multi-Session event stream separately; the finite history API deliberately reads one authorized Session aggregate and does not change global Event publication.
- Decide whether UI-facing Session subscriptions should optionally interleave ephemeral deltas while connected without advancing the durable cursor.
- Add provider-aware context control for provider-executed tool results. Generic text truncation cannot replace provider-native structured payloads that must round-trip exactly.