From f6978db60553716a9974b9e85f855bae8124905d Mon Sep 17 00:00:00 2001 From: Adam Mansfield Date: Mon, 20 Apr 2026 02:35:37 -0400 Subject: [PATCH 01/20] chore(turbo): pass through PATHEXT (#2184) --- turbo.json | 1 + 1 file changed, 1 insertion(+) diff --git a/turbo.json b/turbo.json index d4d58c15e6d..e6ebeee912f 100644 --- a/turbo.json +++ b/turbo.json @@ -23,6 +23,7 @@ "T3CODE_OTLP_EXPORT_INTERVAL_MS", "T3CODE_OTLP_SERVICE_NAME" ], + "globalPassThroughEnv": ["PATHEXT"], "tasks": { "build": { "dependsOn": ["^build"], From 40b3a800347462405b2d322ed5c0c60331f9e70d Mon Sep 17 00:00:00 2001 From: Adin Schmidt <9936213+adinschmidt@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:45:56 -0400 Subject: [PATCH 02/20] fix(server): trim OpenCode provider model names (#2252) --- apps/server/src/provider/Layers/OpenCodeProvider.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index 5e51eae0282..d43372ec5cd 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -11,6 +11,7 @@ import { ServerSettingsService } from "../../serverSettings.ts"; import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; import { buildServerProvider, + nonEmptyTrimmed, parseGenericCliVersion, providerModelsFromSettings, } from "../providerSnapshot.ts"; @@ -204,10 +205,16 @@ function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray Date: Tue, 21 Apr 2026 11:15:41 -0400 Subject: [PATCH 03/20] fix: enforce opencode >= 1.14.19 and reveal window on Wayland (#2262) --- apps/desktop/src/main.ts | 22 +++--- apps/desktop/src/windowReveal.test.ts | 74 +++++++++++++++++++ apps/desktop/src/windowReveal.ts | 28 +++++++ .../provider/Layers/OpenCodeProvider.test.ts | 33 ++++++++- .../src/provider/Layers/OpenCodeProvider.ts | 31 ++++++++ 5 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 apps/desktop/src/windowReveal.test.ts create mode 100644 apps/desktop/src/windowReveal.ts diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 529ed55d03f..c5507c6fb03 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -75,6 +75,7 @@ import { } from "./updateMachine.ts"; import { isArm64HostRunningIntelBuild, resolveDesktopRuntimeInfo } from "./runtimeArch.ts"; import { resolveDesktopAppBranding } from "./appBranding.ts"; +import { bindFirstRevealTrigger, type RevealSubscription } from "./windowReveal.ts"; syncShellEnvironment(); @@ -1998,16 +1999,17 @@ function createWindow(): BrowserWindow { emitUpdateState(); }); - let initialRevealScheduled = false; - const revealInitialWindow = () => { - if (initialRevealScheduled) { - return; - } - initialRevealScheduled = true; - revealWindow(window); - }; - - window.once("ready-to-show", revealInitialWindow); + // On Linux/Wayland with `show: false`, Electron's `ready-to-show` only + // fires after `show()` is called, deadlocking the standard "wait for + // ready, then show" pattern. Add `did-finish-load` as a Linux-only + // fallback so the window still surfaces once the renderer has loaded + // the page. Other platforms keep the no-flash `ready-to-show` path, + // since `did-finish-load` typically fires before the first paint there. + const revealSubscribers: RevealSubscription[] = [(fire) => window.once("ready-to-show", fire)]; + if (process.platform === "linux") { + revealSubscribers.push((fire) => window.webContents.once("did-finish-load", fire)); + } + bindFirstRevealTrigger(revealSubscribers, () => revealWindow(window)); if (isDevelopment) { void window.loadURL(resolveDesktopDevServerUrl()); diff --git a/apps/desktop/src/windowReveal.test.ts b/apps/desktop/src/windowReveal.test.ts new file mode 100644 index 00000000000..88285fec0cb --- /dev/null +++ b/apps/desktop/src/windowReveal.test.ts @@ -0,0 +1,74 @@ +import { EventEmitter } from "node:events"; + +import { describe, expect, it, vi } from "vitest"; + +import { bindFirstRevealTrigger } from "./windowReveal.ts"; + +describe("bindFirstRevealTrigger", () => { + it("reveals when the first trigger fires", () => { + const window = new EventEmitter(); + const webContents = new EventEmitter(); + const reveal = vi.fn(); + + bindFirstRevealTrigger( + [ + (fire) => window.once("ready-to-show", fire), + (fire) => webContents.once("did-finish-load", fire), + ], + reveal, + ); + + window.emit("ready-to-show"); + + expect(reveal).toHaveBeenCalledTimes(1); + }); + + it("reveals when only the fallback trigger fires (Wayland deadlock case)", () => { + const window = new EventEmitter(); + const webContents = new EventEmitter(); + const reveal = vi.fn(); + + bindFirstRevealTrigger( + [ + (fire) => window.once("ready-to-show", fire), + (fire) => webContents.once("did-finish-load", fire), + ], + reveal, + ); + + webContents.emit("did-finish-load"); + + expect(reveal).toHaveBeenCalledTimes(1); + }); + + it("only reveals once when multiple triggers fire", () => { + const window = new EventEmitter(); + const webContents = new EventEmitter(); + const reveal = vi.fn(); + + bindFirstRevealTrigger( + [ + (fire) => window.once("ready-to-show", fire), + (fire) => webContents.once("did-finish-load", fire), + ], + reveal, + ); + + webContents.emit("did-finish-load"); + window.emit("ready-to-show"); + + expect(reveal).toHaveBeenCalledTimes(1); + }); + + it("subscribers using `once` ignore re-emitted events after reveal", () => { + const window = new EventEmitter(); + const reveal = vi.fn(); + + bindFirstRevealTrigger([(fire) => window.once("ready-to-show", fire)], reveal); + + window.emit("ready-to-show"); + window.emit("ready-to-show"); + + expect(reveal).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/desktop/src/windowReveal.ts b/apps/desktop/src/windowReveal.ts new file mode 100644 index 00000000000..8faf65aeb15 --- /dev/null +++ b/apps/desktop/src/windowReveal.ts @@ -0,0 +1,28 @@ +export type RevealSubscription = (listener: () => void) => void; + +/** + * Wire a reveal callback to fire exactly once, on whichever of the provided + * event subscribers fires first. Each subscriber is responsible for binding + * its own event source. + * + * Used by the desktop main window's first-paint reveal logic. The standard + * Electron pattern is to wait for `ready-to-show` before calling `show()`, + * but on Linux/Wayland with `show: false`, `ready-to-show` only fires after + * `show()` is called, deadlocking that pattern. Subscribing to both + * `ready-to-show` and `did-finish-load` (or any other "renderer is alive" + * signal) lets the window surface reliably across platforms. + */ +export function bindFirstRevealTrigger( + subscribers: readonly RevealSubscription[], + reveal: () => void, +): void { + let revealed = false; + const fire = () => { + if (revealed) return; + revealed = true; + reveal(); + }; + for (const subscribe of subscribers) { + subscribe(fire); + } +} diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts index ffce7084342..4a43c37b686 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts @@ -16,9 +16,12 @@ import { import { OpenCodeProviderLive } from "./OpenCodeProvider.ts"; import type { OpenCodeInventory } from "../opencodeRuntime.ts"; +const DEFAULT_VERSION_STDOUT = "opencode 1.14.19\n"; + const runtimeMock = { state: { runVersionError: null as Error | null, + versionStdout: DEFAULT_VERSION_STDOUT, inventoryError: null as Error | null, inventory: { providerList: { connected: [] as string[], all: [] as unknown[], default: {} }, @@ -27,6 +30,7 @@ const runtimeMock = { }, reset() { this.state.runVersionError = null; + this.state.versionStdout = DEFAULT_VERSION_STDOUT; this.state.inventoryError = null; this.state.inventory = { providerList: { connected: [], all: [] as unknown[], default: {} }, @@ -56,7 +60,7 @@ const OpenCodeRuntimeTestDouble: OpenCodeRuntimeShape = { cause: runtimeMock.state.runVersionError, }), ) - : Effect.succeed({ stdout: "opencode 1.0.0\n", stderr: "", code: 0 }), + : Effect.succeed({ stdout: runtimeMock.state.versionStdout, stderr: "", code: 0 }), createOpenCodeSdkClient: () => ({}) as unknown as ReturnType, loadOpenCodeInventory: () => @@ -108,6 +112,33 @@ it.layer(makeTestLayer())("OpenCodeProviderLive", (it) => { }), ); + it.effect("refuses to probe when opencode is older than the required minimum", () => + Effect.gen(function* () { + runtimeMock.state.versionStdout = "opencode 1.4.7\n"; + const provider = yield* OpenCodeProvider; + const snapshot = yield* provider.refresh; + + assert.equal(snapshot.status, "error"); + assert.equal(snapshot.installed, true); + assert.equal(snapshot.version, "1.4.7"); + assert.ok(snapshot.message?.includes("1.14.19")); + assert.ok(snapshot.message?.toLowerCase().includes("upgrade")); + }), + ); + + it.effect("refuses to probe when opencode --version output is unparseable", () => + Effect.gen(function* () { + runtimeMock.state.versionStdout = "garbled binary output\n"; + const provider = yield* OpenCodeProvider; + const snapshot = yield* provider.refresh; + + assert.equal(snapshot.status, "error"); + assert.equal(snapshot.installed, true); + assert.equal(snapshot.version, null); + assert.ok(snapshot.message?.includes("1.14.19")); + }), + ); + it.effect("emits OpenCode variant defaults so trait picker can resolve a visible selection", () => Effect.gen(function* () { runtimeMock.state.inventory = { diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index d43372ec5cd..586bc3d6f48 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -15,6 +15,7 @@ import { parseGenericCliVersion, providerModelsFromSettings, } from "../providerSnapshot.ts"; +import { compareCliVersions } from "../cliVersion.ts"; import { OpenCodeProvider } from "../Services/OpenCodeProvider.ts"; import { OpenCodeRuntime, @@ -24,6 +25,7 @@ import { import type { Agent, ProviderListResponse } from "@opencode-ai/sdk/v2"; const PROVIDER = "opencode" as const; +const MINIMUM_OPENCODE_VERSION = "1.14.19"; class OpenCodeProbeError extends Data.TaggedError("OpenCodeProbeError")<{ readonly cause: unknown; @@ -354,6 +356,35 @@ export const OpenCodeProviderLive = Layer.effect( return fallback(Cause.squash(versionExit.cause)); } version = parseGenericCliVersion(versionExit.value.stdout) ?? null; + + if (!version) { + return fallback( + new Error( + `Unable to determine OpenCode version from \`opencode --version\` output. T3 Code requires OpenCode v${MINIMUM_OPENCODE_VERSION} or newer.`, + ), + null, + ); + } + if (compareCliVersions(version, MINIMUM_OPENCODE_VERSION) < 0) { + return buildServerProvider({ + provider: PROVIDER, + enabled: input.settings.enabled, + checkedAt, + models: providerModelsFromSettings( + [], + PROVIDER, + customModels, + DEFAULT_OPENCODE_MODEL_CAPABILITIES, + ), + probe: { + installed: true, + version, + status: "error", + auth: { status: "unknown" }, + message: `OpenCode v${version} is too old. Upgrade to v${MINIMUM_OPENCODE_VERSION} or newer.`, + }, + }); + } } const inventoryExit = yield* Effect.exit( From 3a1daa87ac103da0c176426e82eb576d87046bdf Mon Sep 17 00:00:00 2001 From: Noxire <59626436+noxire-dev@users.noreply.github.com> Date: Tue, 21 Apr 2026 18:32:08 +0300 Subject: [PATCH 04/20] Add close buttons to toasts (#2023) Co-authored-by: Julius Marminge --- .../BranchToolbarBranchSelector.tsx | 26 +- apps/web/src/components/ChatMarkdown.tsx | 38 +- apps/web/src/components/ChatView.tsx | 54 ++- apps/web/src/components/CommandPalette.tsx | 62 ++- .../components/GitActionsControl.browser.tsx | 1 + apps/web/src/components/GitActionsControl.tsx | 86 ++-- apps/web/src/components/PlanSidebar.tsx | 14 +- apps/web/src/components/Sidebar.tsx | 283 ++++++----- .../components/WebSocketConnectionSurface.tsx | 87 ++-- .../src/components/chat/ProposedPlanCard.tsx | 38 +- .../settings/ConnectionsSettings.tsx | 110 +++-- .../components/settings/SettingsPanels.tsx | 90 ++-- .../components/sidebar/SidebarUpdatePill.tsx | 50 +- .../web/src/components/ui/toast.logic.test.ts | 56 +++ apps/web/src/components/ui/toast.logic.ts | 62 ++- apps/web/src/components/ui/toast.tsx | 453 +++++++++++++----- apps/web/src/components/ui/toastHelpers.ts | 58 +++ apps/web/src/hooks/useThreadActions.ts | 14 +- apps/web/src/routes/__root.tsx | 70 +-- 19 files changed, 1100 insertions(+), 552 deletions(-) create mode 100644 apps/web/src/components/ui/toastHelpers.ts diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx index 996fca4b391..828e6e122ba 100644 --- a/apps/web/src/components/BranchToolbarBranchSelector.tsx +++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx @@ -42,7 +42,7 @@ import { ComboboxStatus, ComboboxTrigger, } from "./ui/combobox"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; interface BranchToolbarBranchSelectorProps { environmentId: EnvironmentId; @@ -351,11 +351,13 @@ export function BranchToolbarBranchSelector({ setThreadBranch(nextBranchName, selectionTarget.nextWorktreePath); } catch (error) { setOptimisticBranch(previousBranch); - toastManager.add({ - type: "error", - title: "Failed to checkout branch.", - description: toBranchActionErrorMessage(error), - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to checkout branch.", + description: toBranchActionErrorMessage(error), + }), + ); } }); }; @@ -381,11 +383,13 @@ export function BranchToolbarBranchSelector({ setThreadBranch(createBranchResult.branch, activeWorktreePath); } catch (error) { setOptimisticBranch(previousBranch); - toastManager.add({ - type: "error", - title: "Failed to create and checkout branch.", - description: toBranchActionErrorMessage(error), - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to create and checkout branch.", + description: toBranchActionErrorMessage(error), + }), + ); } }); }; diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index ba1c944cc87..4b53f8534ff 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -20,7 +20,7 @@ import { defaultUrlTransform } from "react-markdown"; import remarkGfm from "remark-gfm"; import { VscodeEntryIcon } from "./chat/VscodeEntryIcon"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { openInPreferredEditor } from "../editorPreferences"; import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering"; import { fnv1a32 } from "../lib/diffRendering"; @@ -351,21 +351,25 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ } void openInPreferredEditor(api, targetPath).catch((error) => { - toastManager.add({ - type: "error", - title: "Unable to open file", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to open file", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }); }, [targetPath]); const handleCopy = useCallback((value: string, title: string) => { if (typeof window === "undefined" || !navigator.clipboard?.writeText) { - toastManager.add({ - type: "error", - title: `Failed to copy ${title.toLowerCase()}`, - description: "Clipboard API unavailable.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: `Failed to copy ${title.toLowerCase()}`, + description: "Clipboard API unavailable.", + }), + ); return; } @@ -378,11 +382,13 @@ const MarkdownFileLink = memo(function MarkdownFileLink({ }); }, (error) => { - toastManager.add({ - type: "error", - title: `Failed to copy ${title.toLowerCase()}`, - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: `Failed to copy ${title.toLowerCase()}`, + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }, ); }, []); diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 535c0d9fcae..0c76059b6a8 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -101,7 +101,7 @@ import PlanSidebar from "./PlanSidebar"; import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; import { ChevronDownIcon } from "lucide-react"; import { cn, randomUUID } from "~/lib/utils"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { decodeProjectScriptKeybindingRule } from "~/lib/projectScriptKeybindings"; import { type NewProjectScriptInput } from "./ProjectScriptsControl"; import { @@ -1855,11 +1855,13 @@ export default function ChatView(props: ChatViewProps) { title: `Deleted action "${deletedName ?? "Unknown"}"`, }); } catch (error) { - toastManager.add({ - type: "error", - title: "Could not delete action", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not delete action", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); } }, [activeProject, persistProjectScripts], @@ -2426,11 +2428,13 @@ export default function ChatView(props: ChatViewProps) { expiredTerminalContextCount, "empty", ); - toastManager.add({ - type: "warning", - title: toastCopy.title, - description: toastCopy.description, - }); + toastManager.add( + stackedThreadToast({ + type: "warning", + title: toastCopy.title, + description: toastCopy.description, + }), + ); } return; } @@ -2512,11 +2516,13 @@ export default function ChatView(props: ChatViewProps) { expiredTerminalContextCount, "omitted", ); - toastManager.add({ - type: "warning", - title: toastCopy.title, - description: toastCopy.description, - }); + toastManager.add( + stackedThreadToast({ + type: "warning", + title: toastCopy.title, + description: toastCopy.description, + }), + ); } promptRef.current = ""; clearComposerDraftContent(composerDraftTarget); @@ -3082,12 +3088,16 @@ export default function ChatView(props: ChatViewProps) { threadId: nextThreadId, }) .catch(() => undefined); - toastManager.add({ - type: "error", - title: "Could not start implementation thread", - description: - err instanceof Error ? err.message : "An error occurred while creating the new thread.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not start implementation thread", + description: + err instanceof Error + ? err.message + : "An error occurred while creating the new thread.", + }), + ); }) .then(finish, finish); }, [ diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx index 929a9f87e9c..2b587afc510 100644 --- a/apps/web/src/components/CommandPalette.tsx +++ b/apps/web/src/components/CommandPalette.tsx @@ -102,7 +102,7 @@ import { } from "./ui/command"; import { Button } from "./ui/button"; import { Kbd, KbdGroup } from "./ui/kbd"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; @@ -605,11 +605,13 @@ function OpenCommandPaletteDialog() { const environmentId = defaultAddProjectEnvironmentId; if (!environmentId) { - toastManager.add({ - type: "error", - title: "Unable to browse projects", - description: "No environment is available.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to browse projects", + description: "No environment is available.", + }), + ); return; } @@ -724,20 +726,24 @@ function OpenCommandPaletteDialog() { if (!api) return; if (isUnsupportedWindowsProjectPath(rawCwd.trim(), browseEnvironmentPlatform)) { - toastManager.add({ - type: "error", - title: "Failed to add project", - description: "Windows-style paths are only supported on Windows.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to add project", + description: "Windows-style paths are only supported on Windows.", + }), + ); return; } if (isExplicitRelativeProjectPath(rawCwd.trim()) && !currentProjectCwdForBrowse) { - toastManager.add({ - type: "error", - title: "Failed to add project", - description: "Relative paths require an active project.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to add project", + description: "Relative paths require an active project.", + }), + ); return; } @@ -790,11 +796,13 @@ function OpenCommandPaletteDialog() { }).catch(() => undefined); setOpen(false); } catch (error) { - toastManager.add({ - type: "error", - title: "Failed to add project", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to add project", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); } }, [ @@ -934,11 +942,13 @@ function OpenCommandPaletteDialog() { } void item.run().catch((error: unknown) => { - toastManager.add({ - type: "error", - title: "Unable to run command", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to run command", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); }); } diff --git a/apps/web/src/components/GitActionsControl.browser.tsx b/apps/web/src/components/GitActionsControl.browser.tsx index 651b383fa08..beeb136a9c1 100644 --- a/apps/web/src/components/GitActionsControl.browser.tsx +++ b/apps/web/src/components/GitActionsControl.browser.tsx @@ -90,6 +90,7 @@ vi.mock("~/components/ui/toast", () => ({ promise: toastPromiseSpy, update: toastUpdateSpy, }, + stackedThreadToast: vi.fn((options: unknown) => options), })); vi.mock("~/editorPreferences", () => ({ diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 6d2312e4aac..3f39b129acc 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -38,7 +38,7 @@ import { Menu, MenuItem, MenuPopup, MenuTrigger } from "~/components/ui/menu"; import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; import { ScrollArea } from "~/components/ui/scroll-area"; import { Textarea } from "~/components/ui/textarea"; -import { toastManager, type ThreadToastData } from "~/components/ui/toast"; +import { stackedThreadToast, toastManager, type ThreadToastData } from "~/components/ui/toast"; import { openInPreferredEditor } from "~/editorPreferences"; import { gitInitMutationOptions, @@ -474,12 +474,14 @@ export default function GitActionsControl({ return; } void api.shell.openExternal(prUrl).catch((err: unknown) => { - toastManager.add({ - type: "error", - title: "Unable to open PR link", - description: err instanceof Error ? err.message : "An error occurred.", - data: threadToastData, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to open PR link", + description: err instanceof Error ? err.message : "An error occurred.", + ...(threadToastData !== undefined ? { data: threadToastData } : {}), + }), + ); }); }, [gitStatusForActions, threadToastData]); @@ -670,33 +672,44 @@ export default function GitActionsControl({ }; } - const successToastBase = { - type: "success", - title: result.toast.title, - description: result.toast.description, - timeout: 0, - data: { - ...scopedToastData, - dismissAfterVisibleMs: 10_000, - }, - } as const; + const successToastData = { + ...scopedToastData, + dismissAfterVisibleMs: 10_000, + }; if (toastActionProps) { + toastManager.update( + resolvedProgressToastId, + stackedThreadToast({ + type: "success", + title: result.toast.title, + description: result.toast.description, + timeout: 0, + actionProps: toastActionProps, + actionVariant: "outline", + data: successToastData, + }), + ); + } else { toastManager.update(resolvedProgressToastId, { - ...successToastBase, - actionProps: toastActionProps, + type: "success", + title: result.toast.title, + description: result.toast.description, + timeout: 0, + data: successToastData, }); - } else { - toastManager.update(resolvedProgressToastId, successToastBase); } } catch (err) { activeGitActionProgressRef.current = null; - toastManager.update(resolvedProgressToastId, { - type: "error", - title: "Action failed", - description: err instanceof Error ? err.message : "An error occurred.", - data: scopedToastData, - }); + toastManager.update( + resolvedProgressToastId, + stackedThreadToast({ + type: "error", + title: "Action failed", + description: err instanceof Error ? err.message : "An error occurred.", + ...(scopedToastData !== undefined ? { data: scopedToastData } : {}), + }), + ); } }, ); @@ -753,7 +766,10 @@ export default function GitActionsControl({ } if (quickAction.kind === "run_pull") { const promise = pullMutation.mutateAsync(); - toastManager.promise(promise, { + void toastManager.promise< + Awaited>, + ThreadToastData + >(promise, { loading: { title: "Pulling...", data: threadToastData }, success: (result) => ({ title: result.status === "pulled" ? "Pulled" : "Already up to date", @@ -832,12 +848,14 @@ export default function GitActionsControl({ } const target = resolvePathLinkTarget(filePath, gitCwd); void openInPreferredEditor(api, target).catch((error) => { - toastManager.add({ - type: "error", - title: "Unable to open file", - description: error instanceof Error ? error.message : "An error occurred.", - data: threadToastData, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to open file", + description: error instanceof Error ? error.message : "An error occurred.", + ...(threadToastData !== undefined ? { data: threadToastData } : {}), + }), + ); }); }, [gitCwd, threadToastData], diff --git a/apps/web/src/components/PlanSidebar.tsx b/apps/web/src/components/PlanSidebar.tsx index 00b9da2b0c8..afd4bb2e0bc 100644 --- a/apps/web/src/components/PlanSidebar.tsx +++ b/apps/web/src/components/PlanSidebar.tsx @@ -26,7 +26,7 @@ import { } from "../proposedPlan"; import { Menu, MenuItem, MenuPopup, MenuTrigger } from "./ui/menu"; import { readEnvironmentApi } from "~/environmentApi"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; function stepStatusIcon(status: string): React.ReactNode { @@ -112,11 +112,13 @@ const PlanSidebar = memo(function PlanSidebar({ }); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not save plan", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not save plan", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }) .then( () => setIsSavingToWorkspace(false), diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index d25f930c0d5..5d778eec36e 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -94,7 +94,7 @@ import { resolveThreadRouteRef, resolveThreadRouteTarget, } from "../threadRoutes"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { formatRelativeTimeLabel } from "../timestampFormat"; import { SettingsSidebarNav } from "./settings/SettingsSidebarNav"; import { Kbd } from "./ui/kbd"; @@ -950,11 +950,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec }); }, onError: (error) => { - toastManager.add({ - type: "error", - title: "Failed to copy thread ID", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to copy thread ID", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }, }); const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ @@ -968,11 +970,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec }); }, onError: (error) => { - toastManager.add({ - type: "error", - title: "Failed to copy path", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to copy path", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }, }); const openPrLink = useCallback((event: React.MouseEvent, prUrl: string) => { @@ -989,11 +993,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec } void api.shell.openExternal(prUrl).catch((error) => { - toastManager.add({ - type: "error", - title: "Unable to open PR link", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to open PR link", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); }); }, []); const sidebarThreads = useStore( @@ -1299,72 +1305,73 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const memberProjectRef = scopeProjectRef(member.environmentId, member.id); const memberThreadCount = memberThreadCountByPhysicalKey.get(member.physicalProjectKey) ?? 0; if (memberThreadCount > 0) { - const warningToastId = toastManager.add({ - type: "warning", - title: "Project is not empty", - description: "Delete all threads in this project before removing it.", - data: { - actionLayout: "stacked-end", + const warningToastId = toastManager.add( + stackedThreadToast({ + type: "warning", + title: "Project is not empty", + description: "Delete all threads in this project before removing it.", actionVariant: "destructive", - }, - actionProps: { - children: "Delete anyway", - onClick: () => { - void (async () => { - toastManager.close(warningToastId); - await new Promise((resolve) => { - window.setTimeout(resolve, 180); - }); - - const latestProjectThreads = selectSidebarThreadsForProjectRefs( - useStore.getState(), - [memberProjectRef], - ); - const confirmed = await api.dialogs.confirm( - latestProjectThreads.length > 0 - ? [ - `Remove project "${member.name}" and delete its ${latestProjectThreads.length} thread${ - latestProjectThreads.length === 1 ? "" : "s" - }?`, - `Path: ${member.cwd}`, - ...(member.environmentLabel - ? [`Environment: ${member.environmentLabel}`] - : []), - "This permanently clears conversation history for those threads.", - "This removes only this project entry.", - "This action cannot be undone.", - ].join("\n") - : [ - `Remove project "${member.name}"?`, - `Path: ${member.cwd}`, - ...(member.environmentLabel - ? [`Environment: ${member.environmentLabel}`] - : []), - "This removes only this project entry.", - ].join("\n"), - ); - if (!confirmed) { - return; - } + actionProps: { + children: "Delete anyway", + onClick: () => { + void (async () => { + toastManager.close(warningToastId); + await new Promise((resolve) => { + window.setTimeout(resolve, 180); + }); + + const latestProjectThreads = selectSidebarThreadsForProjectRefs( + useStore.getState(), + [memberProjectRef], + ); + const confirmed = await api.dialogs.confirm( + latestProjectThreads.length > 0 + ? [ + `Remove project "${member.name}" and delete its ${latestProjectThreads.length} thread${ + latestProjectThreads.length === 1 ? "" : "s" + }?`, + `Path: ${member.cwd}`, + ...(member.environmentLabel + ? [`Environment: ${member.environmentLabel}`] + : []), + "This permanently clears conversation history for those threads.", + "This removes only this project entry.", + "This action cannot be undone.", + ].join("\n") + : [ + `Remove project "${member.name}"?`, + `Path: ${member.cwd}`, + ...(member.environmentLabel + ? [`Environment: ${member.environmentLabel}`] + : []), + "This removes only this project entry.", + ].join("\n"), + ); + if (!confirmed) { + return; + } - await removeProject(member, { force: true }); - })().catch((error) => { - const message = - error instanceof Error ? error.message : "Unknown error removing project."; - console.error("Failed to remove project", { - projectId: member.id, - environmentId: member.environmentId, - error, - }); - toastManager.add({ - type: "error", - title: `Failed to remove "${member.name}"`, - description: message, + await removeProject(member, { force: true }); + })().catch((error) => { + const message = + error instanceof Error ? error.message : "Unknown error removing project."; + console.error("Failed to remove project", { + projectId: member.id, + environmentId: member.environmentId, + error, + }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: `Failed to remove "${member.name}"`, + description: message, + }), + ); }); - }); + }, }, - }, - }); + }), + ); return; } @@ -1388,11 +1395,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec environmentId: member.environmentId, error, }); - toastManager.add({ - type: "error", - title: `Failed to remove "${member.name}"`, - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: `Failed to remove "${member.name}"`, + description: message, + }), + ); } }, [memberThreadCountByPhysicalKey, removeProject], @@ -1705,11 +1714,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec try { await archiveThread(threadRef); } catch (error) { - toastManager.add({ - type: "error", - title: "Failed to archive thread", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to archive thread", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); } }, [archiveThread], @@ -1757,11 +1768,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec title: trimmed, }); } catch (error) { - toastManager.add({ - type: "error", - title: "Failed to rename thread", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to rename thread", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); } finishRename(); }, @@ -1794,11 +1807,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec const api = readEnvironmentApi(projectRenameTarget.environmentId); if (!api) { - toastManager.add({ - type: "error", - title: "Failed to rename project", - description: "Project API unavailable.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to rename project", + description: "Project API unavailable.", + }), + ); return; } @@ -1811,11 +1826,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec }); closeProjectRenameDialog(); } catch (error) { - toastManager.add({ - type: "error", - title: "Failed to rename project", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to rename project", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); } }, [closeProjectRenameDialog, projectRenameTarget, projectRenameTitle]); @@ -1885,11 +1902,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec } if (clicked === "copy-path") { if (!threadWorkspacePath) { - toastManager.add({ - type: "error", - title: "Path unavailable", - description: "This thread does not have a workspace path to copy.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Path unavailable", + description: "This thread does not have a workspace path to copy.", + }), + ); return; } copyPathToClipboard(threadWorkspacePath, { path: threadWorkspacePath }); @@ -3220,18 +3239,22 @@ export default function Sidebar() { if (!shouldToastDesktopUpdateActionResult(result)) return; const actionError = getDesktopUpdateActionError(result); if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not download update", - description: actionError, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not download update", + description: actionError, + }), + ); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not start update download", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not start update download", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); }); return; } @@ -3247,18 +3270,22 @@ export default function Sidebar() { if (!shouldToastDesktopUpdateActionResult(result)) return; const actionError = getDesktopUpdateActionError(result); if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not install update", - description: actionError, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not install update", + description: actionError, + }), + ); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not install update", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not install update", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); }); } }, [desktopUpdateButtonAction, desktopUpdateButtonDisabled, desktopUpdateState]); diff --git a/apps/web/src/components/WebSocketConnectionSurface.tsx b/apps/web/src/components/WebSocketConnectionSurface.tsx index 7b350e98496..e0bb560980a 100644 --- a/apps/web/src/components/WebSocketConnectionSurface.tsx +++ b/apps/web/src/components/WebSocketConnectionSurface.tsx @@ -10,7 +10,7 @@ import { useWsConnectionStatus, WS_RECONNECT_MAX_ATTEMPTS, } from "../rpc/wsConnectionState"; -import { toastManager } from "./ui/toast"; +import { stackedThreadToast, toastManager } from "./ui/toast"; import { getPrimaryEnvironmentConnection } from "../environments/runtime"; const FORCED_WS_RECONNECT_DEBOUNCE_MS = 5_000; @@ -75,13 +75,34 @@ function describeRecoveredToast( return "Connection restored."; } -function describeSlowRpcAckToast(requests: ReadonlyArray): ReactNode { +function describeSlowRpcAckToast(requests: ReadonlyArray): string { const count = requests.length; const thresholdSeconds = Math.round((requests[0]?.thresholdMs ?? 0) / 1000); return `${count} request${count === 1 ? "" : "s"} waiting longer than ${thresholdSeconds}s.`; } +function SlowRpcAckRequestDetails({ requests }: { requests: ReadonlyArray }) { + return ( +
    + {requests.map((req) => ( +
  • +
    {req.tag}
    +
    + {req.requestId} +
    +
    + Started {formatConnectionMoment(req.startedAt) ?? req.startedAt} +
    +
  • + ))} +
+ ); +} + export function shouldAutoReconnect( status: WsConnectionStatus, trigger: WsAutoReconnectTrigger, @@ -138,15 +159,18 @@ export function WebSocketConnectionCoordinator() { console.warn("Automatic WebSocket reconnect failed", { error }); return; } - toastManager.add({ - type: "error", - title: "Reconnect failed", - description: error instanceof Error ? error.message : "Unable to restart the WebSocket.", - data: { - dismissAfterVisibleMs: 8_000, - hideCopyButton: true, - }, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Reconnect failed", + description: + error instanceof Error ? error.message : "Unable to restart the WebSocket.", + data: { + dismissAfterVisibleMs: 8_000, + hideCopyButton: true, + }, + }), + ); }); }); const syncBrowserOnlineStatus = useEffectEvent(() => { @@ -253,45 +277,45 @@ export function WebSocketConnectionCoordinator() { if (shouldShowReconnectToast || shouldShowOfflineToast || shouldShowExhaustedToast) { const toastPayload = shouldShowOfflineToast - ? { - description: describeOfflineToast(), - timeout: 0, - title: "Offline", - type: "warning" as const, + ? stackedThreadToast({ data: { hideCopyButton: true, }, - } + description: describeOfflineToast(), + timeout: 0, + title: "Offline", + type: "warning", + }) : shouldShowExhaustedToast - ? { + ? stackedThreadToast({ actionProps: { children: "Retry", onClick: triggerManualReconnect, }, - description: describeExhaustedToast(), - timeout: 0, - title: "Disconnected from T3 Server", - type: "error" as const, data: { hideCopyButton: true, }, - } - : { + description: describeExhaustedToast(), + timeout: 0, + title: "Disconnected from T3 Server", + type: "error", + }) + : stackedThreadToast({ actionProps: { children: "Retry now", onClick: triggerManualReconnect, }, + data: { + hideCopyButton: true, + }, description: status.nextRetryAt === null ? `Reconnecting... ${formatReconnectAttemptLabel(status)}` : `Reconnecting in ${formatRetryCountdown(status.nextRetryAt, nowMs)}... ${formatReconnectAttemptLabel(status)}`, timeout: 0, title: buildReconnectTitle(status), - type: "loading" as const, - data: { - hideCopyButton: true, - }, - }; + type: "loading", + }); if (toastIdRef.current) { toastManager.update(toastIdRef.current, toastPayload); @@ -369,6 +393,11 @@ export function SlowRpcAckToastCoordinator() { } const nextToast = { + data: { + expandableContent: , + expandableDescriptionTrigger: true, + expandableLabels: { collapse: "Hide requests", expand: "Show requests" }, + }, description: describeSlowRpcAckToast(slowRequests), timeout: 0, title: "Some requests are slow", diff --git a/apps/web/src/components/chat/ProposedPlanCard.tsx b/apps/web/src/components/chat/ProposedPlanCard.tsx index a36cb097cbe..e53ee93b913 100644 --- a/apps/web/src/components/chat/ProposedPlanCard.tsx +++ b/apps/web/src/components/chat/ProposedPlanCard.tsx @@ -24,7 +24,7 @@ import { DialogPopup, DialogTitle, } from "../ui/dialog"; -import { toastManager } from "../ui/toast"; +import { stackedThreadToast, toastManager } from "../ui/toast"; import { readEnvironmentApi } from "~/environmentApi"; import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; @@ -45,11 +45,13 @@ export const ProposedPlanCard = memo(function ProposedPlanCard({ const [isSavingToWorkspace, setIsSavingToWorkspace] = useState(false); const { copyToClipboard, isCopied } = useCopyToClipboard({ onError: (error) => { - toastManager.add({ - type: "error", - title: "Could not copy plan", - description: error instanceof Error ? error.message : "An error occurred while copying.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not copy plan", + description: error instanceof Error ? error.message : "An error occurred while copying.", + }), + ); }, }); const savePathInputId = useId(); @@ -73,11 +75,13 @@ export const ProposedPlanCard = memo(function ProposedPlanCard({ const openSaveDialog = () => { if (!workspaceRoot) { - toastManager.add({ - type: "error", - title: "Workspace path is unavailable", - description: "This thread does not have a workspace path to save into.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Workspace path is unavailable", + description: "This thread does not have a workspace path to save into.", + }), + ); return; } setSavePath((existing) => (existing.length > 0 ? existing : downloadFilename)); @@ -114,11 +118,13 @@ export const ProposedPlanCard = memo(function ProposedPlanCard({ }); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not save plan", - description: error instanceof Error ? error.message : "An error occurred while saving.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not save plan", + description: error instanceof Error ? error.message : "An error occurred while saving.", + }), + ); }) .then( () => { diff --git a/apps/web/src/components/settings/ConnectionsSettings.tsx b/apps/web/src/components/settings/ConnectionsSettings.tsx index ab31fe7e173..880c4376e2c 100644 --- a/apps/web/src/components/settings/ConnectionsSettings.tsx +++ b/apps/web/src/components/settings/ConnectionsSettings.tsx @@ -41,7 +41,7 @@ import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; import { QRCodeSvg } from "../ui/qr-code"; import { Spinner } from "../ui/spinner"; import { Switch } from "../ui/switch"; -import { toastManager } from "../ui/toast"; +import { stackedThreadToast, toastManager } from "../ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { Button } from "../ui/button"; import { Textarea } from "../ui/textarea"; @@ -302,11 +302,13 @@ const PairingLinkListRow = memo(function PairingLinkListRow({ }, onError: (error) => { setIsRevealDialogOpen(true); - toastManager.add({ - type: "error", - title: canCopyToClipboard ? "Could not copy pairing URL" : "Clipboard copy unavailable", - description: canCopyToClipboard ? error.message : "Showing the full value instead.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: canCopyToClipboard ? "Could not copy pairing URL" : "Clipboard copy unavailable", + description: canCopyToClipboard ? error.message : "Showing the full value instead.", + }), + ); }, }); @@ -535,11 +537,13 @@ const AuthorizedClientsHeaderAction = memo(function AuthorizedClientsHeaderActio setDialogOpen(false); } catch (error) { const message = error instanceof Error ? error.message : "Failed to create pairing URL."; - toastManager.add({ - type: "error", - title: "Could not create pairing URL", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not create pairing URL", + description: message, + }), + ); } finally { setIsCreatingPairingLink(false); } @@ -828,11 +832,13 @@ export function ConnectionsSettings() { error instanceof Error ? error.message : "Failed to update network exposure."; setPendingDesktopServerExposureMode(null); setDesktopServerExposureError(message); - toastManager.add({ - type: "error", - title: "Could not update network access", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not update network access", + description: message, + }), + ); setIsUpdatingDesktopServerExposure(false); } }, @@ -853,11 +859,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to revoke pairing link."; setDesktopAccessManagementError(message); - toastManager.add({ - type: "error", - title: "Could not revoke pairing link", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not revoke pairing link", + description: message, + }), + ); } finally { setRevokingDesktopPairingLinkId(null); } @@ -872,11 +880,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to revoke client access."; setDesktopAccessManagementError(message); - toastManager.add({ - type: "error", - title: "Could not revoke client access", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not revoke client access", + description: message, + }), + ); } finally { setRevokingDesktopClientSessionId(null); } @@ -897,11 +907,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to revoke other clients."; setDesktopAccessManagementError(message); - toastManager.add({ - type: "error", - title: "Could not revoke other clients", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not revoke other clients", + description: message, + }), + ); } finally { setIsRevokingOtherDesktopClients(false); } @@ -933,11 +945,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to add backend."; setSavedBackendError(message); - toastManager.add({ - type: "error", - title: "Could not add backend", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not add backend", + description: message, + }), + ); } finally { setIsAddingSavedBackend(false); } @@ -957,11 +971,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to reconnect backend."; setSavedBackendError(message); - toastManager.add({ - type: "error", - title: "Could not reconnect backend", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not reconnect backend", + description: message, + }), + ); } finally { setReconnectingSavedEnvironmentId(null); } @@ -975,11 +991,13 @@ export function ConnectionsSettings() { } catch (error) { const message = error instanceof Error ? error.message : "Failed to remove backend."; setSavedBackendError(message); - toastManager.add({ - type: "error", - title: "Could not remove backend", - description: message, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not remove backend", + description: message, + }), + ); } finally { setRemovingSavedEnvironmentId(null); } diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 230b0a9965d..b1e7cf81f6f 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -63,7 +63,7 @@ import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from ".. import { Input } from "../ui/input"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { Switch } from "../ui/switch"; -import { toastManager } from "../ui/toast"; +import { stackedThreadToast, toastManager } from "../ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { SettingResetButton, @@ -282,11 +282,13 @@ function AboutVersionSection() { setDesktopUpdateStateQueryData(queryClient, state); }) .catch((error: unknown) => { - toastManager.add({ - type: "error", - title: "Could not change update track", - description: error instanceof Error ? error.message : "Update track change failed.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not change update track", + description: error instanceof Error ? error.message : "Update track change failed.", + }), + ); }) .finally(() => { setIsChangingUpdateChannel(false); @@ -308,11 +310,13 @@ function AboutVersionSection() { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { - toastManager.add({ - type: "error", - title: "Could not download update", - description: error instanceof Error ? error.message : "Download failed.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not download update", + description: error instanceof Error ? error.message : "Download failed.", + }), + ); }); return; } @@ -330,11 +334,13 @@ function AboutVersionSection() { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { - toastManager.add({ - type: "error", - title: "Could not install update", - description: error instanceof Error ? error.message : "Install failed.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not install update", + description: error instanceof Error ? error.message : "Install failed.", + }), + ); }); return; } @@ -345,20 +351,24 @@ function AboutVersionSection() { .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); if (!result.checked) { - toastManager.add({ - type: "error", - title: "Could not check for updates", - description: - result.state.message ?? "Automatic updates are not available in this build.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not check for updates", + description: + result.state.message ?? "Automatic updates are not available in this build.", + }), + ); } }) .catch((error: unknown) => { - toastManager.add({ - type: "error", - title: "Could not check for updates", - description: error instanceof Error ? error.message : "Update check failed.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not check for updates", + description: error instanceof Error ? error.message : "Update check failed.", + }), + ); }); }, [queryClient, updateState]); @@ -1688,11 +1698,13 @@ export function ArchivedThreadsPanel() { try { await unarchiveThread(threadRef); } catch (error) { - toastManager.add({ - type: "error", - title: "Failed to unarchive thread", - description: error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to unarchive thread", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); } return; } @@ -1756,12 +1768,14 @@ export function ArchivedThreadsPanel() { onClick={() => void unarchiveThread(scopeThreadRef(thread.environmentId, thread.id)).catch( (error) => { - toastManager.add({ - type: "error", - title: "Failed to unarchive thread", - description: - error instanceof Error ? error.message : "An error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to unarchive thread", + description: + error instanceof Error ? error.message : "An error occurred.", + }), + ); }, ) } diff --git a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx index 2f9aec112a9..d7e5b74d42d 100644 --- a/apps/web/src/components/sidebar/SidebarUpdatePill.tsx +++ b/apps/web/src/components/sidebar/SidebarUpdatePill.tsx @@ -6,7 +6,7 @@ import { setDesktopUpdateStateQueryData, useDesktopUpdateState, } from "../../lib/desktopUpdateReactQuery"; -import { toastManager } from "../ui/toast"; +import { stackedThreadToast, toastManager } from "../ui/toast"; import { getArm64IntelBuildWarningDescription, getDesktopUpdateActionError, @@ -55,18 +55,22 @@ export function SidebarUpdatePill() { if (!shouldToastDesktopUpdateActionResult(result)) return; const actionError = getDesktopUpdateActionError(result); if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not download update", - description: actionError, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not download update", + description: actionError, + }), + ); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not start update download", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not start update download", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); }); return; } @@ -81,18 +85,22 @@ export function SidebarUpdatePill() { if (!shouldToastDesktopUpdateActionResult(result)) return; const actionError = getDesktopUpdateActionError(result); if (!actionError) return; - toastManager.add({ - type: "error", - title: "Could not install update", - description: actionError, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not install update", + description: actionError, + }), + ); }) .catch((error) => { - toastManager.add({ - type: "error", - title: "Could not install update", - description: error instanceof Error ? error.message : "An unexpected error occurred.", - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Could not install update", + description: error instanceof Error ? error.message : "An unexpected error occurred.", + }), + ); }); } }, [action, disabled, queryClient, state]); diff --git a/apps/web/src/components/ui/toast.logic.test.ts b/apps/web/src/components/ui/toast.logic.test.ts index e634d7d9463..fefb61196bb 100644 --- a/apps/web/src/components/ui/toast.logic.test.ts +++ b/apps/web/src/components/ui/toast.logic.test.ts @@ -45,6 +45,62 @@ describe("buildVisibleToastLayout", () => { ); }); + it("reflows live toasts forward when the front toast is dismissed", () => { + const visibleToasts = [ + { id: "a", height: 48, transitionStatus: "ending" as const }, + { id: "b", height: 72 }, + { id: "c", height: 24 }, + ]; + + const layout = buildVisibleToastLayout(visibleToasts); + + // frontmost height should be the first live toast, not the ending one + assert.equal(layout.frontmostHeight, 72); + assert.deepEqual( + layout.items.map(({ toast, visibleIndex, offsetY }) => ({ + id: toast.id, + visibleIndex, + offsetY, + })), + [ + // Ending toast stays at its front slot; data-ending-style drives its exit + { id: "a", visibleIndex: 0, offsetY: 0 }, + // Live toasts get fresh indices starting at 0 so they move up in sync + { id: "b", visibleIndex: 0, offsetY: 0 }, + { id: "c", visibleIndex: 1, offsetY: 72 }, + ], + ); + }); + + it("keeps a non-front ending toast at its current slot so it exits straight", () => { + const visibleToasts = [ + { id: "a", height: 48 }, + { id: "b", height: 72, transitionStatus: "ending" as const }, + { id: "c", height: 24 }, + ]; + + const layout = buildVisibleToastLayout(visibleToasts); + + // front toast stays, so frontmost height is unchanged + assert.equal(layout.frontmostHeight, 48); + assert.deepEqual( + layout.items.map(({ toast, visibleIndex, offsetY }) => ({ + id: toast.id, + visibleIndex, + offsetY, + })), + [ + // Front live toast — unaffected + { id: "a", visibleIndex: 0, offsetY: 0 }, + // Ending toast keeps its pre-dismissal slot so its horizontal exit + // originates from where the user saw it (not from Y=0). + { id: "b", visibleIndex: 1, offsetY: 48 }, + // Live toast behind "b" slides forward into the vacated slot. + { id: "c", visibleIndex: 1, offsetY: 48 }, + ], + ); + }); + it("treats missing heights as zero", () => { const layout = buildVisibleToastLayout([ { id: "a" }, diff --git a/apps/web/src/components/ui/toast.logic.ts b/apps/web/src/components/ui/toast.logic.ts index 09905a7a670..80f23970cee 100644 --- a/apps/web/src/components/ui/toast.logic.ts +++ b/apps/web/src/components/ui/toast.logic.ts @@ -14,6 +14,12 @@ type ToastWithHeight = { height?: number | null | undefined; }; +type ToastWithTransitionStatus = { + transitionStatus?: "starting" | "ending" | undefined; +}; + +type ToastWithLayoutProps = ToastWithHeight & ToastWithTransitionStatus; + type VisibleToastLayoutItem = { toast: TToast; visibleIndex: number; @@ -21,25 +27,61 @@ type VisibleToastLayoutItem = { }; export function buildVisibleToastLayout( - visibleToasts: readonly (TToast & ToastWithHeight)[], + visibleToasts: readonly (TToast & ToastWithLayoutProps)[], ): { frontmostHeight: number; - items: VisibleToastLayoutItem[]; + items: VisibleToastLayoutItem[]; } { - let offsetY = 0; + // Two parallel cursors: + // - `full*` advances on every toast, so an ending toast keeps the slot it + // occupied before dismissal and its data-ending-style exit transform + // originates from the correct position (critical for dismissing a + // non-front toast in the expanded stack — otherwise it would snap to + // Y=0 and slide off diagonally). + // - `live*` advances only on non-ending toasts, so live toasts reflow + // past the vacated slot in parallel with the exit animation instead of + // waiting for it to finish (which caused a visible "stop and bump"). + let fullIndex = 0; + let fullOffsetY = 0; + let liveIndex = 0; + let liveOffsetY = 0; + + const items: VisibleToastLayoutItem[] = visibleToasts.map( + (toast) => { + const height = normalizeToastHeight(toast.height); + + if (toast.transitionStatus === "ending") { + const item = { + toast, + visibleIndex: fullIndex, + offsetY: fullOffsetY, + }; + fullOffsetY += height; + fullIndex += 1; + return item; + } - return { - frontmostHeight: normalizeToastHeight(visibleToasts[0]?.height), - items: visibleToasts.map((toast, visibleIndex) => { const item = { toast, - visibleIndex, - offsetY, + visibleIndex: liveIndex, + offsetY: liveOffsetY, }; - offsetY += normalizeToastHeight(toast.height); + fullOffsetY += height; + fullIndex += 1; + liveOffsetY += height; + liveIndex += 1; return item; - }), + }, + ); + + // Frontmost height should reflect the first non-ending (live) toast so the + // stack sizes to what's actually staying on screen. + const frontmostLiveToast = visibleToasts.find((toast) => toast.transitionStatus !== "ending"); + + return { + frontmostHeight: normalizeToastHeight(frontmostLiveToast?.height), + items, }; } diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 083ca480079..dd0eba432b7 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -1,17 +1,27 @@ "use client"; import { Toast } from "@base-ui/react/toast"; -import { useEffect, useMemo, type CSSProperties } from "react"; +import { + useEffect, + useMemo, + useState, + type CSSProperties, + type KeyboardEvent, + type ReactNode, +} from "react"; import { useParams } from "@tanstack/react-router"; import { type ScopedThreadRef, type ThreadId } from "@t3tools/contracts"; import { CheckIcon, + ChevronDownIcon, + ChevronUpIcon, CircleAlertIcon, CircleCheckIcon, CopyIcon, InfoIcon, LoaderCircleIcon, TriangleAlertIcon, + XIcon, } from "lucide-react"; import { cn } from "~/lib/utils"; @@ -31,6 +41,11 @@ export type ThreadToastData = { tooltipStyle?: boolean; dismissAfterVisibleMs?: number; hideCopyButton?: boolean; + /** Optional extra body shown after toggling “Show details” (e.g. a list of pending RPCs). */ + expandableContent?: ReactNode; + expandableLabels?: { expand?: string; collapse?: string }; + /** When set with `expandableContent`, the summary + label act as one text disclosure (no separate chevron row). */ + expandableDescriptionTrigger?: boolean; actionLayout?: "inline" | "stacked-end"; actionVariant?: | "default" @@ -55,25 +70,265 @@ const TOAST_ICONS = { warning: TriangleAlertIcon, } as const; +/** Visually shorten long error bodies; clipboard copy still uses the full `description` string. */ +const ERROR_DESCRIPTION_CLAMP_MIN_CHARS = 180; +function errorDescriptionClampClass(type: unknown, description: unknown): string | undefined { + if (type !== "error" || typeof description !== "string") { + return undefined; + } + if (description.length < ERROR_DESCRIPTION_CLAMP_MIN_CHARS) { + return undefined; + } + return "line-clamp-4"; +} + +/** Dismiss-only: circular control overlapping the card corner (iOS notification–style). */ +const toastCornerDismissClass = "absolute z-20 -top-1.5 -right-1.5"; +const toastCornerOrbClass = cn( + "inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-popover/92 text-muted-foreground shadow-sm outline-none backdrop-blur-sm", + "transition-[color,background-color,box-shadow] hover:bg-popover hover:text-foreground", + "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background", +); + function CopyErrorButton({ text }: { text: string }) { const { copyToClipboard, isCopied } = useCopyToClipboard(); return ( ); } +/** Scrollable cap for long expandable lists (~10rem); keeps the toast from growing without bound. */ +const toastExpandablePanelClassName = + "mt-2 max-h-40 min-h-0 overflow-y-auto overscroll-contain pr-0.5 select-text"; + +function ToastExpandableSection({ + children, + labels, +}: { + children: ReactNode; + labels: { expand?: string; collapse?: string }; +}) { + const [open, setOpen] = useState(false); + const expandLabel = labels.expand ?? "Show details"; + const collapseLabel = labels.collapse ?? "Hide details"; + + return ( +
+ + {open ?
{children}
: null} +
+ ); +} + +function ToastDescriptionAndExpandable({ + toastData, + toastDescription, + toastType, +}: { + toastData: ThreadToastData | undefined; + toastDescription: unknown; + toastType: unknown; +}) { + const expandableContent = toastData?.expandableContent; + const labels = toastData?.expandableLabels ?? {}; + const descriptionTrigger = toastData?.expandableDescriptionTrigger ?? false; + const descriptionClassName = cn( + "min-w-0 select-text wrap-break-word text-muted-foreground", + errorDescriptionClampClass(toastType, toastDescription), + ); + const [open, setOpen] = useState(false); + + if (!expandableContent) { + return ; + } + + if (!descriptionTrigger) { + return ( + <> + + {expandableContent} + + ); + } + + const expandLabel = labels.expand ?? "Show details"; + const collapseLabel = labels.collapse ?? "Hide details"; + + const toggle = () => setOpen((v) => !v); + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + toggle(); + } + }; + + return ( + <> +
+
+ +
+ {open ? ( + + ) : ( + + )} +
+ {open ?
{expandableContent}
: null} + + ); +} + +type ToastIconComponent = (typeof TOAST_ICONS)[keyof typeof TOAST_ICONS]; + +interface ToastBodyDescriptor { + readonly Icon: ToastIconComponent | null | undefined; + readonly stackedActionLayout: boolean; + readonly actionVariant: NonNullable; + readonly copyErrorText: string | null; + readonly hasTrailingControls: boolean; + readonly inlineContentEndPad: string; +} + +function deriveToastBodyDescriptor(toast: { + readonly type?: string | undefined; + readonly description?: unknown; + readonly actionProps?: unknown; + readonly data?: ThreadToastData | undefined; +}): ToastBodyDescriptor { + const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; + const stackedActionLayout = + toast.actionProps !== undefined && toast.data?.actionLayout === "stacked-end"; + const actionVariant: NonNullable = + toast.data?.actionVariant ?? "default"; + const copyErrorText = + toast.type === "error" && typeof toast.description === "string" && !toast.data?.hideCopyButton + ? toast.description + : null; + const hasTrailingControls = copyErrorText !== null || toast.actionProps !== undefined; + const inlineContentEndPad = hasTrailingControls ? "pr-6" : "pr-10"; + return { + Icon, + stackedActionLayout, + actionVariant, + copyErrorText, + hasTrailingControls, + inlineContentEndPad, + }; +} + +interface ToastBodyContentProps extends ToastBodyDescriptor { + readonly actionProps: { readonly children?: ReactNode } | undefined; + readonly toastData: ThreadToastData | undefined; + readonly toastDescription: unknown; + readonly toastType: unknown; +} + +function ToastBodyContent({ + stackedActionLayout, + Icon, + copyErrorText, + actionProps, + actionVariant, + hasTrailingControls, + toastData, + toastDescription, + toastType, +}: ToastBodyContentProps) { + return ( + <> +
+ {Icon && ( +
+ +
+ )} +
+ + +
+
+ {hasTrailingControls ? ( +
+ {copyErrorText !== null ? : null} + {actionProps ? ( + + {actionProps.children} + + ) : null} +
+ ) : null} + + ); +} + type ToastPosition = | "top-left" | "top-center" @@ -236,19 +491,17 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { } > {visibleToastLayout.items.map(({ toast, visibleIndex, offsetY }) => { - const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; const hideCollapsedContent = shouldHideCollapsedToastContent( visibleIndex, visibleToastLayout.items.length, ); - const stackedActionLayout = - toast.actionProps !== undefined && toast.data?.actionLayout === "stacked-end"; - const actionVariant = toast.data?.actionVariant ?? "default"; + const bodyDescriptor = deriveToastBodyDescriptor(toast); + const { stackedActionLayout, inlineContentEndPad } = bodyDescriptor; return ( 0 + ? "not-data-expanded:[--toast-calc-height:var(--toast-frontmost-height)] data-expanded:[--toast-calc-height:max(var(--toast-frontmost-height,var(--toast-height)),var(--toast-height))]" + : "[--toast-calc-height:max(var(--toast-frontmost-height,var(--toast-height)),var(--toast-height))]", + "[--toast-gap:--spacing(3)] [--toast-peek:--spacing(3)] [--toast-scale:calc(max(0,1-(var(--toast-index)*.1)))] [--toast-shrink:calc(1-var(--toast-scale))]", + // Root height: never `min-h-(--toast-height)` — Base UI measures height by briefly forcing + // `height: auto` on this node; an old `min-height` from `--toast-height` blocks shrinking, + // so `recalculateHeight` keeps the inflated value after an expandable closes. + // Behind + collapsed: fixed peek. Otherwise natural height (expand/collapse, hover stack). + visibleIndex > 0 + ? "not-data-expanded:h-(--toast-calc-height) data-expanded:h-auto" + : "h-auto", // Define offset-y variable "data-[position*=top]:[--toast-calc-offset-y:calc(var(--toast-offset-y)+var(--toast-index)*var(--toast-gap)+var(--toast-swipe-movement-y))]", "data-[position*=bottom]:[--toast-calc-offset-y:calc(var(--toast-offset-y)*-1+var(--toast-index)*var(--toast-gap)*-1+var(--toast-swipe-movement-y))]", @@ -272,8 +533,7 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { "data-[position*=bottom]:transform-[translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--toast-peek))-(var(--toast-shrink)*var(--toast-calc-height))))_scale(var(--toast-scale))]", // Limited state "data-limited:opacity-0", - // Expanded state - "data-expanded:h-(--toast-height)", + // Expanded stack "data-position:data-expanded:transform-[translateX(var(--toast-swipe-movement-x))_translateY(var(--toast-calc-offset-y))]", // Starting and ending animations "data-[position*=top]:data-starting-style:transform-[translateY(calc(-100%-var(--toast-inset)))]", @@ -314,54 +574,36 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { dismissAfterVisibleMs={toast.data?.dismissAfterVisibleMs} toastId={toast.id} /> +
+ +
-
- {Icon && ( -
- -
- )} - -
-
- - {toast.type === "error" && - typeof toast.description === "string" && - !toast.data?.hideCopyButton && } -
- -
-
- {toast.actionProps && ( - - {toast.actionProps.children} - - )} +
); @@ -390,12 +632,10 @@ function AnchoredToasts() { {toasts .filter((toast) => shouldRenderThreadScopedToast(toast.data, activeThreadRef)) .map((toast) => { - const Icon = toast.type ? TOAST_ICONS[toast.type as keyof typeof TOAST_ICONS] : null; const tooltipStyle = toast.data?.tooltipStyle ?? false; const positionerProps = toast.positionerProps; - const stackedActionLayout = - toast.actionProps !== undefined && toast.data?.actionLayout === "stacked-end"; - const actionVariant = toast.data?.actionVariant ?? "default"; + const bodyDescriptor = deriveToastBodyDescriptor(toast); + const { stackedActionLayout, inlineContentEndPad } = bodyDescriptor; if (!positionerProps?.anchor) { return null; @@ -411,7 +651,7 @@ function AnchoredToasts() { > ) : ( - -
- {Icon && ( -
- -
- )} - -
-
- - {toast.type === "error" && - typeof toast.description === "string" && - !toast.data?.hideCopyButton && ( - - )} -
- -
-
- {toast.actionProps && ( - +
+ +
+ + + + )}
@@ -483,6 +707,9 @@ function AnchoredToasts() { ); } +export { stackedThreadToast } from "./toastHelpers"; +export type { StackedThreadToastOptions } from "./toastHelpers"; + export { ToastProvider, type ToastPosition, diff --git a/apps/web/src/components/ui/toastHelpers.ts b/apps/web/src/components/ui/toastHelpers.ts new file mode 100644 index 00000000000..4ec5d14106c --- /dev/null +++ b/apps/web/src/components/ui/toastHelpers.ts @@ -0,0 +1,58 @@ +"use client"; + +import type { ToastManagerAddOptions } from "@base-ui/react/toast"; +import type { ComponentPropsWithoutRef, ReactNode } from "react"; + +import type { ThreadToastData } from "./toast"; + +export type StackedThreadToastOptions = { + type: "error" | "warning" | "success" | "info" | "loading"; + title: ReactNode; + description?: ReactNode; + timeout?: number; + priority?: "low" | "high"; + actionProps?: ComponentPropsWithoutRef<"button">; + /** Merged into `data`; `actionLayout` is always forced to `"stacked-end"` by the helper. */ + actionVariant?: ThreadToastData["actionVariant"]; + data?: Omit; +}; + +/** + * Thread toast using the stacked body + bottom action row (copy for errors, CTA on its own row). + */ +export function stackedThreadToast( + options: StackedThreadToastOptions, +): ToastManagerAddOptions { + const { type, title, description, timeout, priority, actionProps, actionVariant, data } = options; + + // Helper-owned `actionLayout` must win over any caller-provided `data`, so spread + // the caller's data first and apply `actionLayout: "stacked-end"` last. + const mergedData: ThreadToastData = { + ...(data !== undefined ? data : {}), + actionLayout: "stacked-end", + }; + if (actionVariant !== undefined) { + mergedData.actionVariant = actionVariant; + } + + const payload: ToastManagerAddOptions = { + type, + title, + data: mergedData, + }; + + if (description !== undefined) { + payload.description = description; + } + if (timeout !== undefined) { + payload.timeout = timeout; + } + if (priority !== undefined) { + payload.priority = priority; + } + if (actionProps !== undefined) { + payload.actionProps = actionProps; + } + + return payload; +} diff --git a/apps/web/src/hooks/useThreadActions.ts b/apps/web/src/hooks/useThreadActions.ts index 60b1be85a70..de60ffb18dc 100644 --- a/apps/web/src/hooks/useThreadActions.ts +++ b/apps/web/src/hooks/useThreadActions.ts @@ -20,7 +20,7 @@ import { import { useTerminalStateStore } from "../terminalStateStore"; import { buildThreadRouteParams, resolveThreadRouteRef } from "../threadRoutes"; import { formatWorktreePathForDisplay, getOrphanedWorktreePathForThread } from "../worktreeCleanup"; -import { toastManager } from "../components/ui/toast"; +import { stackedThreadToast, toastManager } from "../components/ui/toast"; import { useSettings } from "./useSettings"; export function useThreadActions() { @@ -225,11 +225,13 @@ export function useThreadActions() { worktreePath: orphanedWorktreePath, error, }); - toastManager.add({ - type: "error", - title: "Thread deleted, but worktree removal failed", - description: `Could not remove ${displayWorktreePath ?? orphanedWorktreePath}. ${message}`, - }); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Thread deleted, but worktree removal failed", + description: `Could not remove ${displayWorktreePath ?? orphanedWorktreePath}. ${message}`, + }), + ); } }, [ diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index b0c0713fe3a..87e8667901d 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -19,7 +19,12 @@ import { WebSocketConnectionSurface, } from "../components/WebSocketConnectionSurface"; import { Button } from "../components/ui/button"; -import { AnchoredToastProvider, ToastProvider, toastManager } from "../components/ui/toast"; +import { + AnchoredToastProvider, + stackedThreadToast, + ToastProvider, + toastManager, +} from "../components/ui/toast"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; import { readLocalApi } from "../localApi"; import { useSettings } from "../hooks/useSettings"; @@ -290,37 +295,42 @@ function EventRouter() { return; } - toastManager.add({ - type: "warning", - title: "Invalid keybindings configuration", - description: issue.message, - actionProps: { - children: "Open keybindings.json", - onClick: () => { - const api = readLocalApi(); - if (!api) { - return; - } - - void Promise.resolve(serverConfig ?? api.server.getConfig()) - .then((config) => { - const editor = resolveAndPersistPreferredEditor(config.availableEditors); - if (!editor) { - throw new Error("No available editors found."); - } - return api.shell.openInEditor(config.keybindingsConfigPath, editor); - }) - .catch((error) => { - toastManager.add({ - type: "error", - title: "Unable to open keybindings file", - description: - error instanceof Error ? error.message : "Unknown error opening file.", + toastManager.add( + stackedThreadToast({ + type: "warning", + title: "Invalid keybindings configuration", + description: issue.message, + actionVariant: "outline", + actionProps: { + children: "Open keybindings.json", + onClick: () => { + const api = readLocalApi(); + if (!api) { + return; + } + + void Promise.resolve(serverConfig ?? api.server.getConfig()) + .then((config) => { + const editor = resolveAndPersistPreferredEditor(config.availableEditors); + if (!editor) { + throw new Error("No available editors found."); + } + return api.shell.openInEditor(config.keybindingsConfigPath, editor); + }) + .catch((error) => { + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Unable to open keybindings file", + description: + error instanceof Error ? error.message : "Unknown error opening file.", + }), + ); }); - }); + }, }, - }, - }); + }), + ); }, ); From b7c89cf496d4c47baa3af2f72042eec5885ea770 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 21 Apr 2026 15:30:54 -0700 Subject: [PATCH 05/20] Refresh Codex protocol bindings to `be75785504ff152fa6333e380a2d50642f42fba0` (#2276) Co-authored-by: Cursor Agent --- .../src/provider/Layers/CodexProvider.ts | 4 +- .../provider/Layers/CodexSessionRuntime.ts | 5 +- .../provider/Layers/ProviderRegistry.test.ts | 2 +- .../scripts/generate.ts | 2 +- .../src/_generated/meta.gen.ts | 61 +- .../src/_generated/namespaces.gen.ts | 27 +- .../src/_generated/schema.gen.ts | 8671 ++++++++++++----- 7 files changed, 6115 insertions(+), 2657 deletions(-) diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index cea768266d8..13069952ae6 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -62,7 +62,9 @@ function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["accoun case "plus": return "ChatGPT Plus Subscription"; case "pro": - return "ChatGPT Pro Subscription"; + return "ChatGPT Pro 20x Subscription"; + case "prolite": + return "ChatGPT Pro 5x Subscription"; case "team": return "ChatGPT Team Subscription"; case "self_serve_business_usage_based": diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.ts index 29b22e16f80..fbf4aa3dee4 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.ts @@ -468,6 +468,7 @@ function readNotificationThreadId(notification: CodexServerNotification): string case "item/commandExecution/outputDelta": case "item/commandExecution/terminalInteraction": case "item/fileChange/outputDelta": + case "item/fileChange/patchUpdated": case "serverRequest/resolved": case "item/mcpToolCall/progress": case "item/reasoning/summaryTextDelta": @@ -476,7 +477,8 @@ function readNotificationThreadId(notification: CodexServerNotification): string case "thread/compacted": case "thread/realtime/started": case "thread/realtime/itemAdded": - case "thread/realtime/transcriptUpdated": + case "thread/realtime/transcript/delta": + case "thread/realtime/transcript/done": case "thread/realtime/outputAudio/delta": case "thread/realtime/sdp": case "thread/realtime/error": @@ -530,6 +532,7 @@ function readRouteFields(notification: CodexServerNotification): { case "item/commandExecution/outputDelta": case "item/commandExecution/terminalInteraction": case "item/fileChange/outputDelta": + case "item/fileChange/patchUpdated": case "item/reasoning/summaryTextDelta": case "item/reasoning/summaryPartAdded": case "item/reasoning/textDelta": diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index 2d6855f914b..8fe28351a41 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -179,7 +179,7 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))( assert.strictEqual(status.version, "1.0.0"); assert.strictEqual(status.auth.status, "authenticated"); assert.strictEqual(status.auth.type, "chatgpt"); - assert.strictEqual(status.auth.label, "ChatGPT Pro Subscription"); + assert.strictEqual(status.auth.label, "ChatGPT Pro 20x Subscription"); assert.deepStrictEqual(status.models, [ { slug: "gpt-live-codex", diff --git a/packages/effect-codex-app-server/scripts/generate.ts b/packages/effect-codex-app-server/scripts/generate.ts index 5f0991c39f0..54ce0866f13 100644 --- a/packages/effect-codex-app-server/scripts/generate.ts +++ b/packages/effect-codex-app-server/scripts/generate.ts @@ -6,7 +6,7 @@ import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonS import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; -const UPSTREAM_REF = "dbfe855f4fd0f5dcdf079882652a8efe622b0595"; +const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0"; const USER_AGENT = "effect-codex-app-server-generator"; const GITHUB_API_BASE = "https://api.github.com/repos/openai/codex/contents/codex-rs/app-server-protocol"; diff --git a/packages/effect-codex-app-server/src/_generated/meta.gen.ts b/packages/effect-codex-app-server/src/_generated/meta.gen.ts index d0b73764957..1afa46859ef 100644 --- a/packages/effect-codex-app-server/src/_generated/meta.gen.ts +++ b/packages/effect-codex-app-server/src/_generated/meta.gen.ts @@ -1,5 +1,5 @@ // This file is generated by the effect-codex-app-server package. Do not edit manually. -// Upstream protocol ref: dbfe855f4fd0f5dcdf079882652a8efe622b0595 +// Upstream protocol ref: be75785504ff152fa6333e380a2d50642f42fba0 import * as CodexSchema from "./schema.gen.ts"; @@ -19,10 +19,17 @@ export const CLIENT_REQUEST_METHODS = { "thread/list": "thread/list", "thread/loaded/list": "thread/loaded/list", "thread/read": "thread/read", + "thread/turns/list": "thread/turns/list", + "thread/inject_items": "thread/inject_items", "skills/list": "skills/list", + "marketplace/add": "marketplace/add", + "marketplace/remove": "marketplace/remove", "plugin/list": "plugin/list", "plugin/read": "plugin/read", "app/list": "app/list", + "device/key/create": "device/key/create", + "device/key/public": "device/key/public", + "device/key/sign": "device/key/sign", "fs/readFile": "fs/readFile", "fs/writeFile": "fs/writeFile", "fs/createDirectory": "fs/createDirectory", @@ -52,6 +59,7 @@ export const CLIENT_REQUEST_METHODS = { "account/login/cancel": "account/login/cancel", "account/logout": "account/logout", "account/rateLimits/read": "account/rateLimits/read", + "account/sendAddCreditsNudgeEmail": "account/sendAddCreditsNudgeEmail", "feedback/upload": "feedback/upload", "command/exec": "command/exec", "command/exec/write": "command/exec/write", @@ -113,6 +121,7 @@ export const SERVER_NOTIFICATION_METHODS = { "item/commandExecution/outputDelta": "item/commandExecution/outputDelta", "item/commandExecution/terminalInteraction": "item/commandExecution/terminalInteraction", "item/fileChange/outputDelta": "item/fileChange/outputDelta", + "item/fileChange/patchUpdated": "item/fileChange/patchUpdated", "serverRequest/resolved": "serverRequest/resolved", "item/mcpToolCall/progress": "item/mcpToolCall/progress", "mcpServer/oauthLogin/completed": "mcpServer/oauthLogin/completed", @@ -120,19 +129,22 @@ export const SERVER_NOTIFICATION_METHODS = { "account/updated": "account/updated", "account/rateLimits/updated": "account/rateLimits/updated", "app/list/updated": "app/list/updated", + "externalAgentConfig/import/completed": "externalAgentConfig/import/completed", "fs/changed": "fs/changed", "item/reasoning/summaryTextDelta": "item/reasoning/summaryTextDelta", "item/reasoning/summaryPartAdded": "item/reasoning/summaryPartAdded", "item/reasoning/textDelta": "item/reasoning/textDelta", "thread/compacted": "thread/compacted", "model/rerouted": "model/rerouted", + warning: "warning", deprecationNotice: "deprecationNotice", configWarning: "configWarning", "fuzzyFileSearch/sessionUpdated": "fuzzyFileSearch/sessionUpdated", "fuzzyFileSearch/sessionCompleted": "fuzzyFileSearch/sessionCompleted", "thread/realtime/started": "thread/realtime/started", "thread/realtime/itemAdded": "thread/realtime/itemAdded", - "thread/realtime/transcriptUpdated": "thread/realtime/transcriptUpdated", + "thread/realtime/transcript/delta": "thread/realtime/transcript/delta", + "thread/realtime/transcript/done": "thread/realtime/transcript/done", "thread/realtime/outputAudio/delta": "thread/realtime/outputAudio/delta", "thread/realtime/sdp": "thread/realtime/sdp", "thread/realtime/error": "thread/realtime/error", @@ -163,10 +175,17 @@ export interface ClientRequestParamsByMethod { readonly "thread/list": typeof CodexSchema.V2ThreadListParams.Type; readonly "thread/loaded/list": typeof CodexSchema.V2ThreadLoadedListParams.Type; readonly "thread/read": typeof CodexSchema.V2ThreadReadParams.Type; + readonly "thread/turns/list": typeof CodexSchema.V2ThreadTurnsListParams.Type; + readonly "thread/inject_items": typeof CodexSchema.V2ThreadInjectItemsParams.Type; readonly "skills/list": typeof CodexSchema.V2SkillsListParams.Type; + readonly "marketplace/add": typeof CodexSchema.V2MarketplaceAddParams.Type; + readonly "marketplace/remove": typeof CodexSchema.V2MarketplaceRemoveParams.Type; readonly "plugin/list": typeof CodexSchema.V2PluginListParams.Type; readonly "plugin/read": typeof CodexSchema.V2PluginReadParams.Type; readonly "app/list": typeof CodexSchema.V2AppsListParams.Type; + readonly "device/key/create": typeof CodexSchema.V2DeviceKeyCreateParams.Type; + readonly "device/key/public": typeof CodexSchema.V2DeviceKeyPublicParams.Type; + readonly "device/key/sign": typeof CodexSchema.V2DeviceKeySignParams.Type; readonly "fs/readFile": typeof CodexSchema.V2FsReadFileParams.Type; readonly "fs/writeFile": typeof CodexSchema.V2FsWriteFileParams.Type; readonly "fs/createDirectory": typeof CodexSchema.V2FsCreateDirectoryParams.Type; @@ -196,6 +215,7 @@ export interface ClientRequestParamsByMethod { readonly "account/login/cancel": typeof CodexSchema.V2CancelLoginAccountParams.Type; readonly "account/logout": undefined; readonly "account/rateLimits/read": undefined; + readonly "account/sendAddCreditsNudgeEmail": typeof CodexSchema.V2SendAddCreditsNudgeEmailParams.Type; readonly "feedback/upload": typeof CodexSchema.V2FeedbackUploadParams.Type; readonly "command/exec": typeof CodexSchema.V2CommandExecParams.Type; readonly "command/exec/write": typeof CodexSchema.V2CommandExecWriteParams.Type; @@ -230,10 +250,17 @@ export interface ClientRequestResponsesByMethod { readonly "thread/list": typeof CodexSchema.V2ThreadListResponse.Type; readonly "thread/loaded/list": typeof CodexSchema.V2ThreadLoadedListResponse.Type; readonly "thread/read": typeof CodexSchema.V2ThreadReadResponse.Type; + readonly "thread/turns/list": typeof CodexSchema.V2ThreadTurnsListResponse.Type; + readonly "thread/inject_items": typeof CodexSchema.V2ThreadInjectItemsResponse.Type; readonly "skills/list": typeof CodexSchema.V2SkillsListResponse.Type; + readonly "marketplace/add": typeof CodexSchema.V2MarketplaceAddResponse.Type; + readonly "marketplace/remove": typeof CodexSchema.V2MarketplaceRemoveResponse.Type; readonly "plugin/list": typeof CodexSchema.V2PluginListResponse.Type; readonly "plugin/read": typeof CodexSchema.V2PluginReadResponse.Type; readonly "app/list": typeof CodexSchema.V2AppsListResponse.Type; + readonly "device/key/create": typeof CodexSchema.V2DeviceKeyCreateResponse.Type; + readonly "device/key/public": typeof CodexSchema.V2DeviceKeyPublicResponse.Type; + readonly "device/key/sign": typeof CodexSchema.V2DeviceKeySignResponse.Type; readonly "fs/readFile": typeof CodexSchema.V2FsReadFileResponse.Type; readonly "fs/writeFile": typeof CodexSchema.V2FsWriteFileResponse.Type; readonly "fs/createDirectory": typeof CodexSchema.V2FsCreateDirectoryResponse.Type; @@ -263,6 +290,7 @@ export interface ClientRequestResponsesByMethod { readonly "account/login/cancel": typeof CodexSchema.V2CancelLoginAccountResponse.Type; readonly "account/logout": typeof CodexSchema.V2LogoutAccountResponse.Type; readonly "account/rateLimits/read": typeof CodexSchema.V2GetAccountRateLimitsResponse.Type; + readonly "account/sendAddCreditsNudgeEmail": typeof CodexSchema.V2SendAddCreditsNudgeEmailResponse.Type; readonly "feedback/upload": typeof CodexSchema.V2FeedbackUploadResponse.Type; readonly "command/exec": typeof CodexSchema.V2CommandExecResponse.Type; readonly "command/exec/write": typeof CodexSchema.V2CommandExecWriteResponse.Type; @@ -336,6 +364,7 @@ export interface ServerNotificationParamsByMethod { readonly "item/commandExecution/outputDelta": typeof CodexSchema.V2CommandExecutionOutputDeltaNotification.Type; readonly "item/commandExecution/terminalInteraction": typeof CodexSchema.V2TerminalInteractionNotification.Type; readonly "item/fileChange/outputDelta": typeof CodexSchema.V2FileChangeOutputDeltaNotification.Type; + readonly "item/fileChange/patchUpdated": typeof CodexSchema.V2FileChangePatchUpdatedNotification.Type; readonly "serverRequest/resolved": typeof CodexSchema.V2ServerRequestResolvedNotification.Type; readonly "item/mcpToolCall/progress": typeof CodexSchema.V2McpToolCallProgressNotification.Type; readonly "mcpServer/oauthLogin/completed": typeof CodexSchema.V2McpServerOauthLoginCompletedNotification.Type; @@ -343,19 +372,22 @@ export interface ServerNotificationParamsByMethod { readonly "account/updated": typeof CodexSchema.V2AccountUpdatedNotification.Type; readonly "account/rateLimits/updated": typeof CodexSchema.V2AccountRateLimitsUpdatedNotification.Type; readonly "app/list/updated": typeof CodexSchema.V2AppListUpdatedNotification.Type; + readonly "externalAgentConfig/import/completed": typeof CodexSchema.V2ExternalAgentConfigImportCompletedNotification.Type; readonly "fs/changed": typeof CodexSchema.V2FsChangedNotification.Type; readonly "item/reasoning/summaryTextDelta": typeof CodexSchema.V2ReasoningSummaryTextDeltaNotification.Type; readonly "item/reasoning/summaryPartAdded": typeof CodexSchema.V2ReasoningSummaryPartAddedNotification.Type; readonly "item/reasoning/textDelta": typeof CodexSchema.V2ReasoningTextDeltaNotification.Type; readonly "thread/compacted": typeof CodexSchema.V2ContextCompactedNotification.Type; readonly "model/rerouted": typeof CodexSchema.V2ModelReroutedNotification.Type; + readonly warning: typeof CodexSchema.V2WarningNotification.Type; readonly deprecationNotice: typeof CodexSchema.V2DeprecationNoticeNotification.Type; readonly configWarning: typeof CodexSchema.V2ConfigWarningNotification.Type; readonly "fuzzyFileSearch/sessionUpdated": typeof CodexSchema.FuzzyFileSearchSessionUpdatedNotification.Type; readonly "fuzzyFileSearch/sessionCompleted": typeof CodexSchema.FuzzyFileSearchSessionCompletedNotification.Type; readonly "thread/realtime/started": typeof CodexSchema.V2ThreadRealtimeStartedNotification.Type; readonly "thread/realtime/itemAdded": typeof CodexSchema.V2ThreadRealtimeItemAddedNotification.Type; - readonly "thread/realtime/transcriptUpdated": typeof CodexSchema.V2ThreadRealtimeTranscriptUpdatedNotification.Type; + readonly "thread/realtime/transcript/delta": typeof CodexSchema.V2ThreadRealtimeTranscriptDeltaNotification.Type; + readonly "thread/realtime/transcript/done": typeof CodexSchema.V2ThreadRealtimeTranscriptDoneNotification.Type; readonly "thread/realtime/outputAudio/delta": typeof CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification.Type; readonly "thread/realtime/sdp": typeof CodexSchema.V2ThreadRealtimeSdpNotification.Type; readonly "thread/realtime/error": typeof CodexSchema.V2ThreadRealtimeErrorNotification.Type; @@ -381,10 +413,17 @@ export const CLIENT_REQUEST_PARAMS = { "thread/list": CodexSchema.V2ThreadListParams, "thread/loaded/list": CodexSchema.V2ThreadLoadedListParams, "thread/read": CodexSchema.V2ThreadReadParams, + "thread/turns/list": CodexSchema.V2ThreadTurnsListParams, + "thread/inject_items": CodexSchema.V2ThreadInjectItemsParams, "skills/list": CodexSchema.V2SkillsListParams, + "marketplace/add": CodexSchema.V2MarketplaceAddParams, + "marketplace/remove": CodexSchema.V2MarketplaceRemoveParams, "plugin/list": CodexSchema.V2PluginListParams, "plugin/read": CodexSchema.V2PluginReadParams, "app/list": CodexSchema.V2AppsListParams, + "device/key/create": CodexSchema.V2DeviceKeyCreateParams, + "device/key/public": CodexSchema.V2DeviceKeyPublicParams, + "device/key/sign": CodexSchema.V2DeviceKeySignParams, "fs/readFile": CodexSchema.V2FsReadFileParams, "fs/writeFile": CodexSchema.V2FsWriteFileParams, "fs/createDirectory": CodexSchema.V2FsCreateDirectoryParams, @@ -414,6 +453,7 @@ export const CLIENT_REQUEST_PARAMS = { "account/login/cancel": CodexSchema.V2CancelLoginAccountParams, "account/logout": undefined, "account/rateLimits/read": undefined, + "account/sendAddCreditsNudgeEmail": CodexSchema.V2SendAddCreditsNudgeEmailParams, "feedback/upload": CodexSchema.V2FeedbackUploadParams, "command/exec": CodexSchema.V2CommandExecParams, "command/exec/write": CodexSchema.V2CommandExecWriteParams, @@ -448,10 +488,17 @@ export const CLIENT_REQUEST_RESPONSES = { "thread/list": CodexSchema.V2ThreadListResponse, "thread/loaded/list": CodexSchema.V2ThreadLoadedListResponse, "thread/read": CodexSchema.V2ThreadReadResponse, + "thread/turns/list": CodexSchema.V2ThreadTurnsListResponse, + "thread/inject_items": CodexSchema.V2ThreadInjectItemsResponse, "skills/list": CodexSchema.V2SkillsListResponse, + "marketplace/add": CodexSchema.V2MarketplaceAddResponse, + "marketplace/remove": CodexSchema.V2MarketplaceRemoveResponse, "plugin/list": CodexSchema.V2PluginListResponse, "plugin/read": CodexSchema.V2PluginReadResponse, "app/list": CodexSchema.V2AppsListResponse, + "device/key/create": CodexSchema.V2DeviceKeyCreateResponse, + "device/key/public": CodexSchema.V2DeviceKeyPublicResponse, + "device/key/sign": CodexSchema.V2DeviceKeySignResponse, "fs/readFile": CodexSchema.V2FsReadFileResponse, "fs/writeFile": CodexSchema.V2FsWriteFileResponse, "fs/createDirectory": CodexSchema.V2FsCreateDirectoryResponse, @@ -481,6 +528,7 @@ export const CLIENT_REQUEST_RESPONSES = { "account/login/cancel": CodexSchema.V2CancelLoginAccountResponse, "account/logout": CodexSchema.V2LogoutAccountResponse, "account/rateLimits/read": CodexSchema.V2GetAccountRateLimitsResponse, + "account/sendAddCreditsNudgeEmail": CodexSchema.V2SendAddCreditsNudgeEmailResponse, "feedback/upload": CodexSchema.V2FeedbackUploadResponse, "command/exec": CodexSchema.V2CommandExecResponse, "command/exec/write": CodexSchema.V2CommandExecWriteResponse, @@ -555,6 +603,7 @@ export const SERVER_NOTIFICATION_PARAMS = { "item/commandExecution/outputDelta": CodexSchema.V2CommandExecutionOutputDeltaNotification, "item/commandExecution/terminalInteraction": CodexSchema.V2TerminalInteractionNotification, "item/fileChange/outputDelta": CodexSchema.V2FileChangeOutputDeltaNotification, + "item/fileChange/patchUpdated": CodexSchema.V2FileChangePatchUpdatedNotification, "serverRequest/resolved": CodexSchema.V2ServerRequestResolvedNotification, "item/mcpToolCall/progress": CodexSchema.V2McpToolCallProgressNotification, "mcpServer/oauthLogin/completed": CodexSchema.V2McpServerOauthLoginCompletedNotification, @@ -562,19 +611,23 @@ export const SERVER_NOTIFICATION_PARAMS = { "account/updated": CodexSchema.V2AccountUpdatedNotification, "account/rateLimits/updated": CodexSchema.V2AccountRateLimitsUpdatedNotification, "app/list/updated": CodexSchema.V2AppListUpdatedNotification, + "externalAgentConfig/import/completed": + CodexSchema.V2ExternalAgentConfigImportCompletedNotification, "fs/changed": CodexSchema.V2FsChangedNotification, "item/reasoning/summaryTextDelta": CodexSchema.V2ReasoningSummaryTextDeltaNotification, "item/reasoning/summaryPartAdded": CodexSchema.V2ReasoningSummaryPartAddedNotification, "item/reasoning/textDelta": CodexSchema.V2ReasoningTextDeltaNotification, "thread/compacted": CodexSchema.V2ContextCompactedNotification, "model/rerouted": CodexSchema.V2ModelReroutedNotification, + warning: CodexSchema.V2WarningNotification, deprecationNotice: CodexSchema.V2DeprecationNoticeNotification, configWarning: CodexSchema.V2ConfigWarningNotification, "fuzzyFileSearch/sessionUpdated": CodexSchema.FuzzyFileSearchSessionUpdatedNotification, "fuzzyFileSearch/sessionCompleted": CodexSchema.FuzzyFileSearchSessionCompletedNotification, "thread/realtime/started": CodexSchema.V2ThreadRealtimeStartedNotification, "thread/realtime/itemAdded": CodexSchema.V2ThreadRealtimeItemAddedNotification, - "thread/realtime/transcriptUpdated": CodexSchema.V2ThreadRealtimeTranscriptUpdatedNotification, + "thread/realtime/transcript/delta": CodexSchema.V2ThreadRealtimeTranscriptDeltaNotification, + "thread/realtime/transcript/done": CodexSchema.V2ThreadRealtimeTranscriptDoneNotification, "thread/realtime/outputAudio/delta": CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification, "thread/realtime/sdp": CodexSchema.V2ThreadRealtimeSdpNotification, "thread/realtime/error": CodexSchema.V2ThreadRealtimeErrorNotification, diff --git a/packages/effect-codex-app-server/src/_generated/namespaces.gen.ts b/packages/effect-codex-app-server/src/_generated/namespaces.gen.ts index baa94c87974..fd18e9d4d0b 100644 --- a/packages/effect-codex-app-server/src/_generated/namespaces.gen.ts +++ b/packages/effect-codex-app-server/src/_generated/namespaces.gen.ts @@ -1,5 +1,5 @@ // This file is generated by the effect-codex-app-server package. Do not edit manually. -// Upstream protocol ref: dbfe855f4fd0f5dcdf079882652a8efe622b0595 +// Upstream protocol ref: be75785504ff152fa6333e380a2d50642f42fba0 import * as CodexSchema from "./schema.gen.ts"; @@ -37,6 +37,12 @@ export const v2 = { ConfigWriteResponse: CodexSchema.V2ConfigWriteResponse, ContextCompactedNotification: CodexSchema.V2ContextCompactedNotification, DeprecationNoticeNotification: CodexSchema.V2DeprecationNoticeNotification, + DeviceKeyCreateParams: CodexSchema.V2DeviceKeyCreateParams, + DeviceKeyCreateResponse: CodexSchema.V2DeviceKeyCreateResponse, + DeviceKeyPublicParams: CodexSchema.V2DeviceKeyPublicParams, + DeviceKeyPublicResponse: CodexSchema.V2DeviceKeyPublicResponse, + DeviceKeySignParams: CodexSchema.V2DeviceKeySignParams, + DeviceKeySignResponse: CodexSchema.V2DeviceKeySignResponse, ErrorNotification: CodexSchema.V2ErrorNotification, ExperimentalFeatureEnablementSetParams: CodexSchema.V2ExperimentalFeatureEnablementSetParams, ExperimentalFeatureEnablementSetResponse: CodexSchema.V2ExperimentalFeatureEnablementSetResponse, @@ -44,11 +50,14 @@ export const v2 = { ExperimentalFeatureListResponse: CodexSchema.V2ExperimentalFeatureListResponse, ExternalAgentConfigDetectParams: CodexSchema.V2ExternalAgentConfigDetectParams, ExternalAgentConfigDetectResponse: CodexSchema.V2ExternalAgentConfigDetectResponse, + ExternalAgentConfigImportCompletedNotification: + CodexSchema.V2ExternalAgentConfigImportCompletedNotification, ExternalAgentConfigImportParams: CodexSchema.V2ExternalAgentConfigImportParams, ExternalAgentConfigImportResponse: CodexSchema.V2ExternalAgentConfigImportResponse, FeedbackUploadParams: CodexSchema.V2FeedbackUploadParams, FeedbackUploadResponse: CodexSchema.V2FeedbackUploadResponse, FileChangeOutputDeltaNotification: CodexSchema.V2FileChangeOutputDeltaNotification, + FileChangePatchUpdatedNotification: CodexSchema.V2FileChangePatchUpdatedNotification, FsChangedNotification: CodexSchema.V2FsChangedNotification, FsCopyParams: CodexSchema.V2FsCopyParams, FsCopyResponse: CodexSchema.V2FsCopyResponse, @@ -84,6 +93,10 @@ export const v2 = { LoginAccountParams: CodexSchema.V2LoginAccountParams, LoginAccountResponse: CodexSchema.V2LoginAccountResponse, LogoutAccountResponse: CodexSchema.V2LogoutAccountResponse, + MarketplaceAddParams: CodexSchema.V2MarketplaceAddParams, + MarketplaceAddResponse: CodexSchema.V2MarketplaceAddResponse, + MarketplaceRemoveParams: CodexSchema.V2MarketplaceRemoveParams, + MarketplaceRemoveResponse: CodexSchema.V2MarketplaceRemoveResponse, McpResourceReadParams: CodexSchema.V2McpResourceReadParams, McpResourceReadResponse: CodexSchema.V2McpResourceReadResponse, McpServerOauthLoginCompletedNotification: CodexSchema.V2McpServerOauthLoginCompletedNotification, @@ -112,6 +125,8 @@ export const v2 = { ReasoningTextDeltaNotification: CodexSchema.V2ReasoningTextDeltaNotification, ReviewStartParams: CodexSchema.V2ReviewStartParams, ReviewStartResponse: CodexSchema.V2ReviewStartResponse, + SendAddCreditsNudgeEmailParams: CodexSchema.V2SendAddCreditsNudgeEmailParams, + SendAddCreditsNudgeEmailResponse: CodexSchema.V2SendAddCreditsNudgeEmailResponse, ServerRequestResolvedNotification: CodexSchema.V2ServerRequestResolvedNotification, SkillsChangedNotification: CodexSchema.V2SkillsChangedNotification, SkillsConfigWriteParams: CodexSchema.V2SkillsConfigWriteParams, @@ -127,6 +142,8 @@ export const v2 = { ThreadCompactStartResponse: CodexSchema.V2ThreadCompactStartResponse, ThreadForkParams: CodexSchema.V2ThreadForkParams, ThreadForkResponse: CodexSchema.V2ThreadForkResponse, + ThreadInjectItemsParams: CodexSchema.V2ThreadInjectItemsParams, + ThreadInjectItemsResponse: CodexSchema.V2ThreadInjectItemsResponse, ThreadListParams: CodexSchema.V2ThreadListParams, ThreadListResponse: CodexSchema.V2ThreadListResponse, ThreadLoadedListParams: CodexSchema.V2ThreadLoadedListParams, @@ -143,8 +160,9 @@ export const v2 = { CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification: CodexSchema.V2ThreadRealtimeSdpNotification, ThreadRealtimeStartedNotification: CodexSchema.V2ThreadRealtimeStartedNotification, - ThreadRealtimeTranscriptUpdatedNotification: - CodexSchema.V2ThreadRealtimeTranscriptUpdatedNotification, + ThreadRealtimeTranscriptDeltaNotification: + CodexSchema.V2ThreadRealtimeTranscriptDeltaNotification, + ThreadRealtimeTranscriptDoneNotification: CodexSchema.V2ThreadRealtimeTranscriptDoneNotification, ThreadResumeParams: CodexSchema.V2ThreadResumeParams, ThreadResumeResponse: CodexSchema.V2ThreadResumeResponse, ThreadRollbackParams: CodexSchema.V2ThreadRollbackParams, @@ -158,6 +176,8 @@ export const v2 = { ThreadStartResponse: CodexSchema.V2ThreadStartResponse, ThreadStatusChangedNotification: CodexSchema.V2ThreadStatusChangedNotification, ThreadTokenUsageUpdatedNotification: CodexSchema.V2ThreadTokenUsageUpdatedNotification, + ThreadTurnsListParams: CodexSchema.V2ThreadTurnsListParams, + ThreadTurnsListResponse: CodexSchema.V2ThreadTurnsListResponse, ThreadUnarchivedNotification: CodexSchema.V2ThreadUnarchivedNotification, ThreadUnarchiveParams: CodexSchema.V2ThreadUnarchiveParams, ThreadUnarchiveResponse: CodexSchema.V2ThreadUnarchiveResponse, @@ -173,6 +193,7 @@ export const v2 = { TurnStartResponse: CodexSchema.V2TurnStartResponse, TurnSteerParams: CodexSchema.V2TurnSteerParams, TurnSteerResponse: CodexSchema.V2TurnSteerResponse, + WarningNotification: CodexSchema.V2WarningNotification, WindowsSandboxSetupCompletedNotification: CodexSchema.V2WindowsSandboxSetupCompletedNotification, WindowsSandboxSetupStartParams: CodexSchema.V2WindowsSandboxSetupStartParams, WindowsSandboxSetupStartResponse: CodexSchema.V2WindowsSandboxSetupStartResponse, diff --git a/packages/effect-codex-app-server/src/_generated/schema.gen.ts b/packages/effect-codex-app-server/src/_generated/schema.gen.ts index 5f2119a19b8..3af37c0785f 100644 --- a/packages/effect-codex-app-server/src/_generated/schema.gen.ts +++ b/packages/effect-codex-app-server/src/_generated/schema.gen.ts @@ -1,5 +1,5 @@ // This file is generated by the effect-codex-app-server package. Do not edit manually. -// Upstream protocol ref: dbfe855f4fd0f5dcdf079882652a8efe622b0595 +// Upstream protocol ref: be75785504ff152fa6333e380a2d50642f42fba0 import * as Schema from "effect/Schema"; @@ -45,6 +45,9 @@ export const ClientRequest__AbsolutePathBuf = Schema.String.annotate({ "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", }); +export type ClientRequest__AddCreditsNudgeCreditType = "credits" | "usage_limit"; +export const ClientRequest__AddCreditsNudgeCreditType = Schema.Literals(["credits", "usage_limit"]); + export type ClientRequest__ApprovalsReviewer = "user" | "guardian_subagent"; export const ClientRequest__ApprovalsReviewer = Schema.Literals([ "user", @@ -234,27 +237,20 @@ export const ClientRequest__ConfigReadParams = Schema.Struct({ includeLayers: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), }); -export type ClientRequest__ContentItem = - | { readonly text: string; readonly type: "input_text" } - | { readonly image_url: string; readonly type: "input_image" } - | { readonly text: string; readonly type: "output_text" }; -export const ClientRequest__ContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), - }).annotate({ title: "InputTextContentItem" }), - Schema.Struct({ - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), - }).annotate({ title: "InputImageContentItem" }), - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), - }).annotate({ title: "OutputTextContentItem" }), - ], - { mode: "oneOf" }, -); +export type ClientRequest__DeviceKeyProtectionPolicy = + | "hardware_only" + | "allow_os_protected_nonextractable"; +export const ClientRequest__DeviceKeyProtectionPolicy = Schema.Literals([ + "hardware_only", + "allow_os_protected_nonextractable", +]).annotate({ + description: "Protection policy for creating or loading a controller-local device key.", +}); + +export type ClientRequest__DeviceKeyPublicParams = { readonly keyId: string }; +export const ClientRequest__DeviceKeyPublicParams = Schema.Struct({ + keyId: Schema.String, +}).annotate({ description: "Fetch a controller-local device key public key by id." }); export type ClientRequest__ExperimentalFeatureEnablementSetParams = { readonly enablement: { readonly [x: string]: boolean }; @@ -316,11 +312,13 @@ export type ClientRequest__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" | "CONFIG" | "SKILLS" + | "PLUGINS" | "MCP_SERVER_CONFIG"; export const ClientRequest__ExternalAgentConfigMigrationItemType = Schema.Literals([ "AGENTS_MD", "CONFIG", "SKILLS", + "PLUGINS", "MCP_SERVER_CONFIG", ]); @@ -329,6 +327,7 @@ export type ClientRequest__FeedbackUploadParams = { readonly extraLogFiles?: ReadonlyArray | null; readonly includeLogs: boolean; readonly reason?: string | null; + readonly tags?: { readonly [x: string]: string } | null; readonly threadId?: string | null; }; export const ClientRequest__FeedbackUploadParams = Schema.Struct({ @@ -336,6 +335,9 @@ export const ClientRequest__FeedbackUploadParams = Schema.Struct({ extraLogFiles: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), includeLogs: Schema.Boolean, reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + tags: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); @@ -612,14 +614,30 @@ export const ClientRequest__LoginAccountParams = Schema.Union( { mode: "oneOf" }, ); +export type ClientRequest__MarketplaceAddParams = { + readonly refName?: string | null; + readonly source: string; + readonly sparsePaths?: ReadonlyArray | null; +}; +export const ClientRequest__MarketplaceAddParams = Schema.Struct({ + refName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: Schema.String, + sparsePaths: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), +}); + +export type ClientRequest__MarketplaceRemoveParams = { readonly marketplaceName: string }; +export const ClientRequest__MarketplaceRemoveParams = Schema.Struct({ + marketplaceName: Schema.String, +}); + export type ClientRequest__McpResourceReadParams = { readonly server: string; - readonly threadId: string; + readonly threadId?: string | null; readonly uri: string; }; export const ClientRequest__McpResourceReadParams = Schema.Struct({ server: Schema.String, - threadId: Schema.String, + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), uri: Schema.String, }); @@ -708,17 +726,16 @@ export const ClientRequest__ModelListParams = Schema.Struct({ export type ClientRequest__Personality = "none" | "friendly" | "pragmatic"; export const ClientRequest__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); -export type ClientRequest__PluginUninstallParams = { - readonly forceRemoteSync?: boolean; - readonly pluginId: string; +export type ClientRequest__PluginUninstallParams = { readonly pluginId: string }; +export const ClientRequest__PluginUninstallParams = Schema.Struct({ pluginId: Schema.String }); + +export type ClientRequest__PluginsMigration = { + readonly marketplaceName: string; + readonly pluginNames: ReadonlyArray; }; -export const ClientRequest__PluginUninstallParams = Schema.Struct({ - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, apply the remote plugin change before the local uninstall flow.", - }), - ), - pluginId: Schema.String, +export const ClientRequest__PluginsMigration = Schema.Struct({ + marketplaceName: Schema.String, + pluginNames: Schema.Array(Schema.String), }); export type ClientRequest__ReasoningEffort = @@ -787,6 +804,18 @@ export const ClientRequest__ReasoningSummary = Schema.Union( "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", }); +export type ClientRequest__RemoteControlClientConnectionAudience = + "remote_control_client_websocket"; +export const ClientRequest__RemoteControlClientConnectionAudience = Schema.Literal( + "remote_control_client_websocket", +).annotate({ description: "Audience for a remote-control client connection device-key proof." }); + +export type ClientRequest__RemoteControlClientEnrollmentAudience = + "remote_control_client_enrollment"; +export const ClientRequest__RemoteControlClientEnrollmentAudience = Schema.Literal( + "remote_control_client_enrollment", +).annotate({ description: "Audience for a remote-control client enrollment device-key proof." }); + export type ClientRequest__RequestId = string | number; export const ClientRequest__RequestId = Schema.Union([ Schema.String, @@ -903,6 +932,9 @@ export const ClientRequest__SkillsListExtraRootsForCwd = Schema.Struct({ extraUserRoots: Schema.Array(Schema.String), }); +export type ClientRequest__SortDirection = "asc" | "desc"; +export const ClientRequest__SortDirection = Schema.Literals(["asc", "desc"]); + export type ClientRequest__TextElement = { readonly byteRange: { readonly end: number; readonly start: number }; readonly placeholder?: string | null; @@ -934,6 +966,17 @@ export const ClientRequest__ThreadArchiveParams = Schema.Struct({ threadId: Sche export type ClientRequest__ThreadCompactStartParams = { readonly threadId: string }; export const ClientRequest__ThreadCompactStartParams = Schema.Struct({ threadId: Schema.String }); +export type ClientRequest__ThreadInjectItemsParams = { + readonly items: ReadonlyArray; + readonly threadId: string; +}; +export const ClientRequest__ThreadInjectItemsParams = Schema.Struct({ + items: Schema.Array(Schema.Unknown).annotate({ + description: "Raw Responses API items to append to the thread's model-visible history.", + }), + threadId: Schema.String, +}); + export type ClientRequest__ThreadLoadedListParams = { readonly cursor?: string | null; readonly limit?: number | null; @@ -1106,44 +1149,47 @@ export const CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), }); -export type CommandExecutionRequestApprovalParams__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const CommandExecutionRequestApprovalParams__CommandAction = Schema.Union( +export type CommandExecutionRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "none"; +export const CommandExecutionRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "none", +]); + +export type CommandExecutionRequestApprovalParams__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const CommandExecutionRequestApprovalParams__FileSystemSpecialPath = Schema.Union( [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), ], { mode: "oneOf" }, ); @@ -1404,6 +1450,51 @@ export const PermissionsRequestApprovalParams__AdditionalNetworkPermissions = Sc enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), }); +export type PermissionsRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "none"; +export const PermissionsRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "none", +]); + +export type PermissionsRequestApprovalParams__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const PermissionsRequestApprovalParams__FileSystemSpecialPath = Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, +); + export type PermissionsRequestApprovalResponse__AbsolutePathBuf = string; export const PermissionsRequestApprovalResponse__AbsolutePathBuf = Schema.String.annotate({ description: @@ -1417,6 +1508,51 @@ export const PermissionsRequestApprovalResponse__AdditionalNetworkPermissions = enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), }); +export type PermissionsRequestApprovalResponse__FileSystemAccessMode = "read" | "write" | "none"; +export const PermissionsRequestApprovalResponse__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "none", +]); + +export type PermissionsRequestApprovalResponse__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const PermissionsRequestApprovalResponse__FileSystemSpecialPath = Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, +); + export type ServerNotification__AbsolutePathBuf = string; export const ServerNotification__AbsolutePathBuf = Schema.String.annotate({ description: @@ -1434,6 +1570,13 @@ export const ServerNotification__AccountLoginCompletedNotification = Schema.Stru success: Schema.Boolean, }); +export type ServerNotification__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const ServerNotification__AdditionalNetworkPermissions = Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +}); + export type ServerNotification__AgentMessageDeltaNotification = { readonly delta: string; readonly itemId: string; @@ -1490,7 +1633,7 @@ export const ServerNotification__AuthMode = Schema.Literals([ export type ServerNotification__AutoReviewDecisionSource = "agent"; export const ServerNotification__AutoReviewDecisionSource = Schema.Literal("agent").annotate({ - description: "[UNSTABLE] Source that produced a terminal guardian approval review decision.", + description: "[UNSTABLE] Source that produced a terminal approval auto-review decision.", }); export type ServerNotification__CollabAgentStatus = @@ -1511,48 +1654,6 @@ export const ServerNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type ServerNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const ServerNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type ServerNotification__CommandExecOutputDeltaNotification = { readonly capReached: boolean; readonly deltaBase64: string; @@ -1666,6 +1767,9 @@ export const ServerNotification__DynamicToolCallStatus = Schema.Literals([ "failed", ]); +export type ServerNotification__ExternalAgentConfigImportCompletedNotification = {}; +export const ServerNotification__ExternalAgentConfigImportCompletedNotification = Schema.Struct({}); + export type ServerNotification__FileChangeOutputDeltaNotification = { readonly delta: string; readonly itemId: string; @@ -1679,6 +1783,47 @@ export const ServerNotification__FileChangeOutputDeltaNotification = Schema.Stru turnId: Schema.String, }); +export type ServerNotification__FileSystemAccessMode = "read" | "write" | "none"; +export const ServerNotification__FileSystemAccessMode = Schema.Literals(["read", "write", "none"]); + +export type ServerNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const ServerNotification__FileSystemSpecialPath = Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, +); + export type ServerNotification__FuzzyFileSearchMatchType = "file" | "directory"; export const ServerNotification__FuzzyFileSearchMatchType = Schema.Literals(["file", "directory"]); @@ -1712,7 +1857,7 @@ export const ServerNotification__GuardianApprovalReviewStatus = Schema.Literals( "denied", "timedOut", "aborted", -]).annotate({ description: "[UNSTABLE] Lifecycle state for a guardian approval review." }); +]).annotate({ description: "[UNSTABLE] Lifecycle state for an approval auto-review." }); export type ServerNotification__GuardianCommandSource = "shell" | "unifiedExec"; export const ServerNotification__GuardianCommandSource = Schema.Literals(["shell", "unifiedExec"]); @@ -1723,7 +1868,7 @@ export const ServerNotification__GuardianRiskLevel = Schema.Literals([ "medium", "high", "critical", -]).annotate({ description: "[UNSTABLE] Risk level assigned by guardian approval review." }); +]).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); export type ServerNotification__GuardianUserAuthorization = "unknown" | "low" | "medium" | "high"; export const ServerNotification__GuardianUserAuthorization = Schema.Literals([ @@ -1731,18 +1876,18 @@ export const ServerNotification__GuardianUserAuthorization = Schema.Literals([ "low", "medium", "high", -]).annotate({ - description: "[UNSTABLE] Authorization level assigned by guardian approval review.", -}); +]).annotate({ description: "[UNSTABLE] Authorization level assigned by approval auto-review." }); export type ServerNotification__HookEventName = | "preToolUse" + | "permissionRequest" | "postToolUse" | "sessionStart" | "userPromptSubmit" | "stop"; export const ServerNotification__HookEventName = Schema.Literals([ "preToolUse", + "permissionRequest", "postToolUse", "sessionStart", "userPromptSubmit", @@ -1949,6 +2094,7 @@ export type ServerNotification__PlanType = | "go" | "plus" | "pro" + | "prolite" | "team" | "self_serve_business_usage_based" | "business" @@ -1961,6 +2107,7 @@ export const ServerNotification__PlanType = Schema.Literals([ "go", "plus", "pro", + "prolite", "team", "self_serve_business_usage_based", "business", @@ -1970,6 +2117,20 @@ export const ServerNotification__PlanType = Schema.Literals([ "unknown", ]); +export type ServerNotification__RateLimitReachedType = + | "rate_limit_reached" + | "workspace_owner_credits_depleted" + | "workspace_member_credits_depleted" + | "workspace_owner_usage_limit_reached" + | "workspace_member_usage_limit_reached"; +export const ServerNotification__RateLimitReachedType = Schema.Literals([ + "rate_limit_reached", + "workspace_owner_credits_depleted", + "workspace_member_credits_depleted", + "workspace_owner_usage_limit_reached", + "workspace_member_usage_limit_reached", +]); + export type ServerNotification__RateLimitWindow = { readonly resetsAt?: number | null; readonly usedPercent: number; @@ -2209,20 +2370,34 @@ export const ServerNotification__ThreadRealtimeSdpNotification = Schema.Struct({ description: "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.", }); -export type ServerNotification__ThreadRealtimeTranscriptUpdatedNotification = { +export type ServerNotification__ThreadRealtimeTranscriptDeltaNotification = { + readonly delta: string; readonly role: string; - readonly text: string; readonly threadId: string; }; -export const ServerNotification__ThreadRealtimeTranscriptUpdatedNotification = Schema.Struct({ +export const ServerNotification__ThreadRealtimeTranscriptDeltaNotification = Schema.Struct({ + delta: Schema.String.annotate({ description: "Live transcript delta from the realtime event." }), role: Schema.String, - text: Schema.String, threadId: Schema.String, }).annotate({ description: "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.", }); +export type ServerNotification__ThreadRealtimeTranscriptDoneNotification = { + readonly role: string; + readonly text: string; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeTranscriptDoneNotification = Schema.Struct({ + role: Schema.String, + text: Schema.String.annotate({ description: "Final complete text for the transcript part." }), + threadId: Schema.String, +}).annotate({ + description: + "EXPERIMENTAL - final transcript text emitted when realtime completes a transcript part.", +}); + export type ServerNotification__ThreadUnarchivedNotification = { readonly threadId: string }; export const ServerNotification__ThreadUnarchivedNotification = Schema.Struct({ threadId: Schema.String, @@ -2272,6 +2447,22 @@ export const ServerNotification__TurnStatus = Schema.Literals([ "inProgress", ]); +export type ServerNotification__WarningNotification = { + readonly message: string; + readonly threadId?: string | null; +}; +export const ServerNotification__WarningNotification = Schema.Struct({ + message: Schema.String.annotate({ description: "Concise warning message for the user." }), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional thread target when the warning applies to a specific thread.", + }), + Schema.Null, + ]), + ), +}); + export type ServerNotification__WebSearchAction = | { readonly queries?: ReadonlyArray | null; @@ -2337,68 +2528,28 @@ export const ServerRequest__AdditionalNetworkPermissions = Schema.Struct({ export type ServerRequest__ChatgptAuthTokensRefreshReason = "unauthorized"; export const ServerRequest__ChatgptAuthTokensRefreshReason = Schema.Literal("unauthorized"); -export type ServerRequest__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const ServerRequest__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - -export type ServerRequest__DynamicToolCallParams = { - readonly arguments: unknown; - readonly callId: string; - readonly threadId: string; - readonly tool: string; - readonly turnId: string; -}; -export const ServerRequest__DynamicToolCallParams = Schema.Struct({ - arguments: Schema.Unknown, - callId: Schema.String, - threadId: Schema.String, - tool: Schema.String, - turnId: Schema.String, -}); - -export type ServerRequest__FileChange = - | { readonly content: string; readonly type: "add" } - | { readonly content: string; readonly type: "delete" } - | { readonly move_path?: string | null; readonly type: "update"; readonly unified_diff: string }; -export const ServerRequest__FileChange = Schema.Union( +export type ServerRequest__DynamicToolCallParams = { + readonly arguments: unknown; + readonly callId: string; + readonly namespace?: string | null; + readonly threadId: string; + readonly tool: string; + readonly turnId: string; +}; +export const ServerRequest__DynamicToolCallParams = Schema.Struct({ + arguments: Schema.Unknown, + callId: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + threadId: Schema.String, + tool: Schema.String, + turnId: Schema.String, +}); + +export type ServerRequest__FileChange = + | { readonly content: string; readonly type: "add" } + | { readonly content: string; readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update"; readonly unified_diff: string }; +export const ServerRequest__FileChange = Schema.Union( [ Schema.Struct({ content: Schema.String, @@ -2447,6 +2598,47 @@ export const ServerRequest__FileChangeRequestApprovalParams = Schema.Struct({ turnId: Schema.String, }); +export type ServerRequest__FileSystemAccessMode = "read" | "write" | "none"; +export const ServerRequest__FileSystemAccessMode = Schema.Literals(["read", "write", "none"]); + +export type ServerRequest__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const ServerRequest__FileSystemSpecialPath = Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, +); + export type ServerRequest__McpElicitationArrayType = "array"; export const ServerRequest__McpElicitationArrayType = Schema.Literal("array"); @@ -2619,6 +2811,7 @@ export type V2AccountRateLimitsUpdatedNotification__PlanType = | "go" | "plus" | "pro" + | "prolite" | "team" | "self_serve_business_usage_based" | "business" @@ -2631,6 +2824,7 @@ export const V2AccountRateLimitsUpdatedNotification__PlanType = Schema.Literals( "go", "plus", "pro", + "prolite", "team", "self_serve_business_usage_based", "business", @@ -2640,6 +2834,20 @@ export const V2AccountRateLimitsUpdatedNotification__PlanType = Schema.Literals( "unknown", ]); +export type V2AccountRateLimitsUpdatedNotification__RateLimitReachedType = + | "rate_limit_reached" + | "workspace_owner_credits_depleted" + | "workspace_member_credits_depleted" + | "workspace_owner_usage_limit_reached" + | "workspace_member_usage_limit_reached"; +export const V2AccountRateLimitsUpdatedNotification__RateLimitReachedType = Schema.Literals([ + "rate_limit_reached", + "workspace_owner_credits_depleted", + "workspace_member_credits_depleted", + "workspace_owner_usage_limit_reached", + "workspace_member_usage_limit_reached", +]); + export type V2AccountRateLimitsUpdatedNotification__RateLimitWindow = { readonly resetsAt?: number | null; readonly usedPercent: number; @@ -2667,6 +2875,7 @@ export type V2AccountUpdatedNotification__PlanType = | "go" | "plus" | "pro" + | "prolite" | "team" | "self_serve_business_usage_based" | "business" @@ -2679,6 +2888,7 @@ export const V2AccountUpdatedNotification__PlanType = Schema.Literals([ "go", "plus", "pro", + "prolite", "team", "self_serve_business_usage_based", "business", @@ -3034,6 +3244,63 @@ export const V2ConfigWriteResponse__AbsolutePathBuf = Schema.String.annotate({ export type V2ConfigWriteResponse__WriteStatus = "ok" | "okOverridden"; export const V2ConfigWriteResponse__WriteStatus = Schema.Literals(["ok", "okOverridden"]); +export type V2DeviceKeyCreateParams__DeviceKeyProtectionPolicy = + | "hardware_only" + | "allow_os_protected_nonextractable"; +export const V2DeviceKeyCreateParams__DeviceKeyProtectionPolicy = Schema.Literals([ + "hardware_only", + "allow_os_protected_nonextractable", +]).annotate({ + description: "Protection policy for creating or loading a controller-local device key.", +}); + +export type V2DeviceKeyCreateResponse__DeviceKeyAlgorithm = "ecdsa_p256_sha256"; +export const V2DeviceKeyCreateResponse__DeviceKeyAlgorithm = Schema.Literal( + "ecdsa_p256_sha256", +).annotate({ description: "Device-key algorithm reported at enrollment and signing boundaries." }); + +export type V2DeviceKeyCreateResponse__DeviceKeyProtectionClass = + | "hardware_secure_enclave" + | "hardware_tpm" + | "os_protected_nonextractable"; +export const V2DeviceKeyCreateResponse__DeviceKeyProtectionClass = Schema.Literals([ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable", +]).annotate({ description: "Platform protection class for a controller-local device key." }); + +export type V2DeviceKeyPublicResponse__DeviceKeyAlgorithm = "ecdsa_p256_sha256"; +export const V2DeviceKeyPublicResponse__DeviceKeyAlgorithm = Schema.Literal( + "ecdsa_p256_sha256", +).annotate({ description: "Device-key algorithm reported at enrollment and signing boundaries." }); + +export type V2DeviceKeyPublicResponse__DeviceKeyProtectionClass = + | "hardware_secure_enclave" + | "hardware_tpm" + | "os_protected_nonextractable"; +export const V2DeviceKeyPublicResponse__DeviceKeyProtectionClass = Schema.Literals([ + "hardware_secure_enclave", + "hardware_tpm", + "os_protected_nonextractable", +]).annotate({ description: "Platform protection class for a controller-local device key." }); + +export type V2DeviceKeySignParams__RemoteControlClientConnectionAudience = + "remote_control_client_websocket"; +export const V2DeviceKeySignParams__RemoteControlClientConnectionAudience = Schema.Literal( + "remote_control_client_websocket", +).annotate({ description: "Audience for a remote-control client connection device-key proof." }); + +export type V2DeviceKeySignParams__RemoteControlClientEnrollmentAudience = + "remote_control_client_enrollment"; +export const V2DeviceKeySignParams__RemoteControlClientEnrollmentAudience = Schema.Literal( + "remote_control_client_enrollment", +).annotate({ description: "Audience for a remote-control client enrollment device-key proof." }); + +export type V2DeviceKeySignResponse__DeviceKeyAlgorithm = "ecdsa_p256_sha256"; +export const V2DeviceKeySignResponse__DeviceKeyAlgorithm = Schema.Literal( + "ecdsa_p256_sha256", +).annotate({ description: "Device-key algorithm reported at enrollment and signing boundaries." }); + export type V2ErrorNotification__NonSteerableTurnKind = "review" | "compact"; export const V2ErrorNotification__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); @@ -3092,17 +3359,57 @@ export type V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationIte | "AGENTS_MD" | "CONFIG" | "SKILLS" + | "PLUGINS" | "MCP_SERVER_CONFIG"; export const V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType = - Schema.Literals(["AGENTS_MD", "CONFIG", "SKILLS", "MCP_SERVER_CONFIG"]); + Schema.Literals(["AGENTS_MD", "CONFIG", "SKILLS", "PLUGINS", "MCP_SERVER_CONFIG"]); + +export type V2ExternalAgentConfigDetectResponse__PluginsMigration = { + readonly marketplaceName: string; + readonly pluginNames: ReadonlyArray; +}; +export const V2ExternalAgentConfigDetectResponse__PluginsMigration = Schema.Struct({ + marketplaceName: Schema.String, + pluginNames: Schema.Array(Schema.String), +}); export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" | "CONFIG" | "SKILLS" + | "PLUGINS" | "MCP_SERVER_CONFIG"; export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = - Schema.Literals(["AGENTS_MD", "CONFIG", "SKILLS", "MCP_SERVER_CONFIG"]); + Schema.Literals(["AGENTS_MD", "CONFIG", "SKILLS", "PLUGINS", "MCP_SERVER_CONFIG"]); + +export type V2ExternalAgentConfigImportParams__PluginsMigration = { + readonly marketplaceName: string; + readonly pluginNames: ReadonlyArray; +}; +export const V2ExternalAgentConfigImportParams__PluginsMigration = Schema.Struct({ + marketplaceName: Schema.String, + pluginNames: Schema.Array(Schema.String), +}); + +export type V2FileChangePatchUpdatedNotification__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2FileChangePatchUpdatedNotification__PatchChangeKind = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), + }).annotate({ title: "AddPatchChangeKind" }), + Schema.Struct({ + type: Schema.Literal("delete").annotate({ title: "DeletePatchChangeKindType" }), + }).annotate({ title: "DeletePatchChangeKind" }), + Schema.Struct({ + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdatePatchChangeKindType" }), + }).annotate({ title: "UpdatePatchChangeKind" }), + ], + { mode: "oneOf" }, +); export type V2FsChangedNotification__AbsolutePathBuf = string; export const V2FsChangedNotification__AbsolutePathBuf = Schema.String.annotate({ @@ -3143,6 +3450,7 @@ export type V2GetAccountRateLimitsResponse__PlanType = | "go" | "plus" | "pro" + | "prolite" | "team" | "self_serve_business_usage_based" | "business" @@ -3155,6 +3463,7 @@ export const V2GetAccountRateLimitsResponse__PlanType = Schema.Literals([ "go", "plus", "pro", + "prolite", "team", "self_serve_business_usage_based", "business", @@ -3164,6 +3473,20 @@ export const V2GetAccountRateLimitsResponse__PlanType = Schema.Literals([ "unknown", ]); +export type V2GetAccountRateLimitsResponse__RateLimitReachedType = + | "rate_limit_reached" + | "workspace_owner_credits_depleted" + | "workspace_member_credits_depleted" + | "workspace_owner_usage_limit_reached" + | "workspace_member_usage_limit_reached"; +export const V2GetAccountRateLimitsResponse__RateLimitReachedType = Schema.Literals([ + "rate_limit_reached", + "workspace_owner_credits_depleted", + "workspace_member_credits_depleted", + "workspace_owner_usage_limit_reached", + "workspace_member_usage_limit_reached", +]); + export type V2GetAccountRateLimitsResponse__RateLimitWindow = { readonly resetsAt?: number | null; readonly usedPercent: number; @@ -3184,6 +3507,7 @@ export type V2GetAccountResponse__PlanType = | "go" | "plus" | "pro" + | "prolite" | "team" | "self_serve_business_usage_based" | "business" @@ -3196,6 +3520,7 @@ export const V2GetAccountResponse__PlanType = Schema.Literals([ "go", "plus", "pro", + "prolite", "team", "self_serve_business_usage_based", "business", @@ -3205,14 +3530,22 @@ export const V2GetAccountResponse__PlanType = Schema.Literals([ "unknown", ]); +export type V2HookCompletedNotification__AbsolutePathBuf = string; +export const V2HookCompletedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2HookCompletedNotification__HookEventName = | "preToolUse" + | "permissionRequest" | "postToolUse" | "sessionStart" | "userPromptSubmit" | "stop"; export const V2HookCompletedNotification__HookEventName = Schema.Literals([ "preToolUse", + "permissionRequest", "postToolUse", "sessionStart", "userPromptSubmit", @@ -3260,14 +3593,22 @@ export const V2HookCompletedNotification__HookRunStatus = Schema.Literals([ export type V2HookCompletedNotification__HookScope = "thread" | "turn"; export const V2HookCompletedNotification__HookScope = Schema.Literals(["thread", "turn"]); +export type V2HookStartedNotification__AbsolutePathBuf = string; +export const V2HookStartedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2HookStartedNotification__HookEventName = | "preToolUse" + | "permissionRequest" | "postToolUse" | "sessionStart" | "userPromptSubmit" | "stop"; export const V2HookStartedNotification__HookEventName = Schema.Literals([ "preToolUse", + "permissionRequest", "postToolUse", "sessionStart", "userPromptSubmit", @@ -3315,6 +3656,12 @@ export const V2HookStartedNotification__HookRunStatus = Schema.Literals([ export type V2HookStartedNotification__HookScope = "thread" | "turn"; export const V2HookStartedNotification__HookScope = Schema.Literals(["thread", "turn"]); +export type V2ItemCompletedNotification__AbsolutePathBuf = string; +export const V2ItemCompletedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ItemCompletedNotification__CollabAgentStatus = | "pendingInit" | "running" @@ -3333,48 +3680,6 @@ export const V2ItemCompletedNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ItemCompletedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ItemCompletedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ItemCompletedNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -3584,12 +3889,71 @@ export const V2ItemCompletedNotification__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf = string; +export const V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf = + Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions = + Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) }); + export type V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource = "agent"; export const V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource = Schema.Literal("agent").annotate({ - description: "[UNSTABLE] Source that produced a terminal guardian approval review decision.", + description: "[UNSTABLE] Source that produced a terminal approval auto-review decision.", }); +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = + | "read" + | "write" + | "none"; +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = + Schema.Literals(["read", "write", "none"]); + +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath = + Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, + ); + export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus = | "inProgress" | "approved" @@ -3598,7 +3962,7 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalR | "aborted"; export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus = Schema.Literals(["inProgress", "approved", "denied", "timedOut", "aborted"]).annotate({ - description: "[UNSTABLE] Lifecycle state for a guardian approval review.", + description: "[UNSTABLE] Lifecycle state for an approval auto-review.", }); export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource = @@ -3614,7 +3978,7 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel | "critical"; export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel = Schema.Literals( ["low", "medium", "high", "critical"], -).annotate({ description: "[UNSTABLE] Risk level assigned by guardian approval review." }); +).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization = | "unknown" @@ -3623,7 +3987,7 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAutho | "high"; export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization = Schema.Literals(["unknown", "low", "medium", "high"]).annotate({ - description: "[UNSTABLE] Authorization level assigned by guardian approval review.", + description: "[UNSTABLE] Authorization level assigned by approval auto-review.", }); export type V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = @@ -3634,6 +3998,64 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalPr export const V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]); +export type V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf = string; +export const V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf = + Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }); + +export type V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions = + Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) }); + +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode = + | "read" + | "write" + | "none"; +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode = + Schema.Literals(["read", "write", "none"]); + +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "current_working_directory" } + | { readonly kind: "project_roots"; readonly subpath?: string | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { readonly kind: "unknown"; readonly path: string; readonly subpath?: string | null }; +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath = Schema.Union( + [ + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("current_working_directory") }).annotate({ + title: "CurrentWorkingDirectoryFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", + }), + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", + }), + Schema.Struct({ + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }), + ], + { mode: "oneOf" }, +); + export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = | "inProgress" | "approved" @@ -3642,7 +4064,7 @@ export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRev | "aborted"; export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = Schema.Literals(["inProgress", "approved", "denied", "timedOut", "aborted"]).annotate({ - description: "[UNSTABLE] Lifecycle state for a guardian approval review.", + description: "[UNSTABLE] Lifecycle state for an approval auto-review.", }); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource = @@ -3661,7 +4083,7 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel "medium", "high", "critical", -]).annotate({ description: "[UNSTABLE] Risk level assigned by guardian approval review." }); +]).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization = | "unknown" @@ -3670,7 +4092,7 @@ export type V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthori | "high"; export const V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization = Schema.Literals(["unknown", "low", "medium", "high"]).annotate({ - description: "[UNSTABLE] Authorization level assigned by guardian approval review.", + description: "[UNSTABLE] Authorization level assigned by approval auto-review.", }); export type V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol = @@ -3681,6 +4103,12 @@ export type V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProt export const V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol = Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]); +export type V2ItemStartedNotification__AbsolutePathBuf = string; +export const V2ItemStartedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ItemStartedNotification__CollabAgentStatus = | "pendingInit" | "running" @@ -3699,48 +4127,6 @@ export const V2ItemStartedNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ItemStartedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ItemStartedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ItemStartedNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -4031,6 +4417,18 @@ export const V2ListMcpServerStatusResponse__Tool = Schema.Struct({ title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ description: "Definition for a tool the client can call." }); +export type V2MarketplaceAddResponse__AbsolutePathBuf = string; +export const V2MarketplaceAddResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + +export type V2MarketplaceRemoveResponse__AbsolutePathBuf = string; +export const V2MarketplaceRemoveResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2McpResourceReadResponse__ResourceContent = | { readonly _meta?: unknown; @@ -4209,50 +4607,11 @@ export const V2PluginReadResponse__PluginInstallPolicy = Schema.Literals([ "INSTALLED_BY_DEFAULT", ]); -export type V2PluginReadResponse__SkillInterface = { - readonly brandColor?: string | null; - readonly defaultPrompt?: string | null; - readonly displayName?: string | null; - readonly iconLarge?: string | null; - readonly iconSmall?: string | null; - readonly shortDescription?: string | null; -}; -export const V2PluginReadResponse__SkillInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - defaultPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconLarge: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconSmall: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2RawResponseItemCompletedNotification__ContentItem = - | { readonly text: string; readonly type: "input_text" } - | { readonly image_url: string; readonly type: "input_image" } - | { readonly text: string; readonly type: "output_text" }; -export const V2RawResponseItemCompletedNotification__ContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), - }).annotate({ title: "InputTextContentItem" }), - Schema.Struct({ - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), - }).annotate({ title: "InputImageContentItem" }), - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), - }).annotate({ title: "OutputTextContentItem" }), - ], - { mode: "oneOf" }, -); - -export type V2RawResponseItemCompletedNotification__GhostCommit = { - readonly id: string; - readonly parent?: string | null; - readonly preexisting_untracked_dirs: ReadonlyArray; - readonly preexisting_untracked_files: ReadonlyArray; +export type V2RawResponseItemCompletedNotification__GhostCommit = { + readonly id: string; + readonly parent?: string | null; + readonly preexisting_untracked_dirs: ReadonlyArray; + readonly preexisting_untracked_files: ReadonlyArray; }; export const V2RawResponseItemCompletedNotification__GhostCommit = Schema.Struct({ id: Schema.String, @@ -4449,6 +4808,12 @@ export const V2ReviewStartParams__ReviewTarget = Schema.Union( { mode: "oneOf" }, ); +export type V2ReviewStartResponse__AbsolutePathBuf = string; +export const V2ReviewStartResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ReviewStartResponse__CollabAgentStatus = | "pendingInit" | "running" @@ -4467,48 +4832,6 @@ export const V2ReviewStartResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ReviewStartResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ReviewStartResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ReviewStartResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -4728,6 +5051,20 @@ export const V2ReviewStartResponse__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType = "credits" | "usage_limit"; +export const V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType = Schema.Literals([ + "credits", + "usage_limit", +]); + +export type V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus = + | "sent" + | "cooldown_active"; +export const V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus = Schema.Literals([ + "sent", + "cooldown_active", +]); + export type V2ServerRequestResolvedNotification__RequestId = string | number; export const V2ServerRequestResolvedNotification__RequestId = Schema.Union([ Schema.String, @@ -4749,6 +5086,12 @@ export const V2SkillsListParams__SkillsListExtraRootsForCwd = Schema.Struct({ extraUserRoots: Schema.Array(Schema.String), }); +export type V2SkillsListResponse__AbsolutePathBuf = string; +export const V2SkillsListResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2SkillsListResponse__SkillErrorInfo = { readonly message: string; readonly path: string; @@ -4758,23 +5101,6 @@ export const V2SkillsListResponse__SkillErrorInfo = Schema.Struct({ path: Schema.String, }); -export type V2SkillsListResponse__SkillInterface = { - readonly brandColor?: string | null; - readonly defaultPrompt?: string | null; - readonly displayName?: string | null; - readonly iconLarge?: string | null; - readonly iconSmall?: string | null; - readonly shortDescription?: string | null; -}; -export const V2SkillsListResponse__SkillInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - defaultPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconLarge: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconSmall: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - export type V2SkillsListResponse__SkillScope = "user" | "repo" | "system" | "admin"; export const V2SkillsListResponse__SkillScope = Schema.Literals([ "user", @@ -4909,48 +5235,6 @@ export const V2ThreadForkResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadForkResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadForkResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadForkResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -5193,6 +5477,9 @@ export const V2ThreadForkResponse__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadListParams__SortDirection = "asc" | "desc"; +export const V2ThreadListParams__SortDirection = Schema.Literals(["asc", "desc"]); + export type V2ThreadListParams__ThreadSortKey = "created_at" | "updated_at"; export const V2ThreadListParams__ThreadSortKey = Schema.Literals(["created_at", "updated_at"]); @@ -5220,6 +5507,12 @@ export const V2ThreadListParams__ThreadSourceKind = Schema.Literals([ "unknown", ]); +export type V2ThreadListResponse__AbsolutePathBuf = string; +export const V2ThreadListResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ThreadListResponse__AgentPath = string; export const V2ThreadListResponse__AgentPath = Schema.String; @@ -5241,48 +5534,6 @@ export const V2ThreadListResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadListResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadListResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadListResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -5557,6 +5808,12 @@ export const V2ThreadMetadataUpdateParams__ThreadMetadataGitInfoUpdateParams = S ), }); +export type V2ThreadMetadataUpdateResponse__AbsolutePathBuf = string; +export const V2ThreadMetadataUpdateResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ThreadMetadataUpdateResponse__AgentPath = string; export const V2ThreadMetadataUpdateResponse__AgentPath = Schema.String; @@ -5578,48 +5835,6 @@ export const V2ThreadMetadataUpdateResponse__CollabAgentStatus = Schema.Literals "notFound", ]); -export type V2ThreadMetadataUpdateResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadMetadataUpdateResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadMetadataUpdateResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -5872,6 +6087,12 @@ export const V2ThreadMetadataUpdateResponse__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadReadResponse__AbsolutePathBuf = string; +export const V2ThreadReadResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ThreadReadResponse__AgentPath = string; export const V2ThreadReadResponse__AgentPath = Schema.String; @@ -5893,48 +6114,6 @@ export const V2ThreadReadResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadReadResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadReadResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadReadResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -6247,28 +6426,6 @@ export const V2ThreadResumeParams__AskForApproval = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadResumeParams__ContentItem = - | { readonly text: string; readonly type: "input_text" } - | { readonly image_url: string; readonly type: "input_image" } - | { readonly text: string; readonly type: "output_text" }; -export const V2ThreadResumeParams__ContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), - }).annotate({ title: "InputTextContentItem" }), - Schema.Struct({ - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), - }).annotate({ title: "InputImageContentItem" }), - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), - }).annotate({ title: "OutputTextContentItem" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadResumeParams__GhostCommit = { readonly id: string; readonly parent?: string | null; @@ -6485,48 +6642,6 @@ export const V2ThreadResumeResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadResumeResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadResumeResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadResumeResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -6769,6 +6884,12 @@ export const V2ThreadResumeResponse__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadRollbackResponse__AbsolutePathBuf = string; +export const V2ThreadRollbackResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ThreadRollbackResponse__AgentPath = string; export const V2ThreadRollbackResponse__AgentPath = Schema.String; @@ -6790,48 +6911,6 @@ export const V2ThreadRollbackResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadRollbackResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadRollbackResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadRollbackResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -7074,6 +7153,12 @@ export const V2ThreadRollbackResponse__WebSearchAction = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadStartedNotification__AbsolutePathBuf = string; +export const V2ThreadStartedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2ThreadStartedNotification__AgentPath = string; export const V2ThreadStartedNotification__AgentPath = Schema.String; @@ -7095,48 +7180,6 @@ export const V2ThreadStartedNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadStartedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadStartedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadStartedNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -7501,48 +7544,6 @@ export const V2ThreadStartResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadStartResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadStartResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2ThreadStartResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -7808,10 +7809,16 @@ export const V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown = Schema totalTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), }); -export type V2ThreadUnarchiveResponse__AgentPath = string; -export const V2ThreadUnarchiveResponse__AgentPath = Schema.String; +export type V2ThreadTurnsListParams__SortDirection = "asc" | "desc"; +export const V2ThreadTurnsListParams__SortDirection = Schema.Literals(["asc", "desc"]); -export type V2ThreadUnarchiveResponse__CollabAgentStatus = +export type V2ThreadTurnsListResponse__AbsolutePathBuf = string; +export const V2ThreadTurnsListResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + +export type V2ThreadTurnsListResponse__CollabAgentStatus = | "pendingInit" | "running" | "interrupted" @@ -7819,7 +7826,7 @@ export type V2ThreadUnarchiveResponse__CollabAgentStatus = | "errored" | "shutdown" | "notFound"; -export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ +export const V2ThreadTurnsListResponse__CollabAgentStatus = Schema.Literals([ "pendingInit", "running", "interrupted", @@ -7829,48 +7836,260 @@ export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2ThreadUnarchiveResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadUnarchiveResponse__CommandAction = Schema.Union( +export type V2ThreadTurnsListResponse__CommandExecutionStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadTurnsListResponse__CommandExecutionStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]); + +export type V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" }; +export const V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +); + +export type V2ThreadTurnsListResponse__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadTurnsListResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]); + +export type V2ThreadTurnsListResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const V2ThreadTurnsListResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}); + +export type V2ThreadTurnsListResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadTurnsListResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}); + +export type V2ThreadTurnsListResponse__McpToolCallResult = { + readonly _meta?: unknown; + readonly content: ReadonlyArray; + readonly structuredContent?: unknown; +}; +export const V2ThreadTurnsListResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Unknown), + content: Schema.Array(Schema.Unknown), + structuredContent: Schema.optionalKey(Schema.Unknown), +}); + +export type V2ThreadTurnsListResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadTurnsListResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]); + +export type V2ThreadTurnsListResponse__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; +}; +export const V2ThreadTurnsListResponse__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + note: Schema.String, + path: Schema.String, +}); + +export type V2ThreadTurnsListResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadTurnsListResponse__MessagePhase = Schema.Literals([ + "commentary", + "final_answer", +]).annotate({ + description: + 'Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as "phase unknown" and keep compatibility behavior for legacy models.', +}); + +export type V2ThreadTurnsListResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadTurnsListResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]); + +export type V2ThreadTurnsListResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadTurnsListResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]); + +export type V2ThreadTurnsListResponse__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2ThreadTurnsListResponse__PatchChangeKind = Schema.Union( + [ Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), + }).annotate({ title: "AddPatchChangeKind" }), + Schema.Struct({ + type: Schema.Literal("delete").annotate({ title: "DeletePatchChangeKindType" }), + }).annotate({ title: "DeletePatchChangeKind" }), + Schema.Struct({ + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdatePatchChangeKindType" }), + }).annotate({ title: "UpdatePatchChangeKind" }), + ], + { mode: "oneOf" }, +); + +export type V2ThreadTurnsListResponse__ReasoningEffort = + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh"; +export const V2ThreadTurnsListResponse__ReasoningEffort = Schema.Literals([ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", +]).annotate({ + description: + "See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning", +}); + +export type V2ThreadTurnsListResponse__TextElement = { + readonly byteRange: { readonly end: number; readonly start: number }; + readonly placeholder?: string | null; +}; +export const V2ThreadTurnsListResponse__TextElement = Schema.Struct({ + byteRange: Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + }).annotate({ + description: "Byte range in the parent `text` buffer that this element occupies.", + }), + placeholder: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional human-readable placeholder for the element, displayed in the UI.", + }), + Schema.Null, + ]), + ), +}); + +export type V2ThreadTurnsListResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadTurnsListResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]); + +export type V2ThreadTurnsListResponse__WebSearchAction = + | { + readonly queries?: ReadonlyArray | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly type: "openPage"; readonly url?: string | null } + | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } + | { readonly type: "other" }; +export const V2ThreadTurnsListResponse__WebSearchAction = Schema.Union( + [ + Schema.Struct({ + queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), + type: Schema.Literal("search").annotate({ title: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "OpenPageWebSearchAction" }), + Schema.Struct({ + pattern: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("findInPage").annotate({ title: "FindInPageWebSearchActionType" }), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "FindInPageWebSearchAction" }), + Schema.Struct({ + type: Schema.Literal("other").annotate({ title: "OtherWebSearchActionType" }), + }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, ); +export type V2ThreadUnarchiveResponse__AbsolutePathBuf = string; +export const V2ThreadUnarchiveResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + +export type V2ThreadUnarchiveResponse__AgentPath = string; +export const V2ThreadUnarchiveResponse__AgentPath = Schema.String; + +export type V2ThreadUnarchiveResponse__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]); + export type V2ThreadUnarchiveResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -8130,6 +8349,12 @@ export const V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus = Schema.Liter "unsubscribed", ]); +export type V2TurnCompletedNotification__AbsolutePathBuf = string; +export const V2TurnCompletedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2TurnCompletedNotification__CollabAgentStatus = | "pendingInit" | "running" @@ -8148,48 +8373,6 @@ export const V2TurnCompletedNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2TurnCompletedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2TurnCompletedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2TurnCompletedNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -8427,6 +8610,12 @@ export const V2TurnPlanUpdatedNotification__TurnPlanStepStatus = Schema.Literals "completed", ]); +export type V2TurnStartedNotification__AbsolutePathBuf = string; +export const V2TurnStartedNotification__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2TurnStartedNotification__CollabAgentStatus = | "pendingInit" | "running" @@ -8445,48 +8634,6 @@ export const V2TurnStartedNotification__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2TurnStartedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2TurnStartedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2TurnStartedNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -8826,6 +8973,12 @@ export const V2TurnStartParams__TextElement = Schema.Struct({ ), }); +export type V2TurnStartResponse__AbsolutePathBuf = string; +export const V2TurnStartResponse__AbsolutePathBuf = Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", +}); + export type V2TurnStartResponse__CollabAgentStatus = | "pendingInit" | "running" @@ -8844,48 +8997,6 @@ export const V2TurnStartResponse__CollabAgentStatus = Schema.Literals([ "notFound", ]); -export type V2TurnStartResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: string; - readonly type: "read"; - } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } - | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly command: string; readonly type: "unknown" }; -export const V2TurnStartResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: Schema.String, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), - Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), - }).annotate({ title: "SearchCommandAction" }), - Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), - ], - { mode: "oneOf" }, -); - export type V2TurnStartResponse__CommandExecutionStatus = | "inProgress" | "completed" @@ -9156,23 +9267,18 @@ export const ApplyPatchApprovalResponse__NetworkPolicyAmendment = Schema.Struct( }); export type ClientRequest__PluginInstallParams = { - readonly forceRemoteSync?: boolean; - readonly marketplacePath: ClientRequest__AbsolutePathBuf; + readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; export const ClientRequest__PluginInstallParams = Schema.Struct({ - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, apply the remote plugin change before the local install flow.", - }), - ), - marketplacePath: ClientRequest__AbsolutePathBuf, + marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); export type ClientRequest__PluginListParams = { readonly cwds?: ReadonlyArray | null; - readonly forceRemoteSync?: boolean; }; export const ClientRequest__PluginListParams = Schema.Struct({ cwds: Schema.optionalKey( @@ -9184,21 +9290,17 @@ export const ClientRequest__PluginListParams = Schema.Struct({ Schema.Null, ]), ), - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "When true, reconcile the official curated marketplace against the remote plugin state before listing marketplaces.", - }), - ), }); export type ClientRequest__PluginReadParams = { - readonly marketplacePath: ClientRequest__AbsolutePathBuf; + readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; export const ClientRequest__PluginReadParams = Schema.Struct({ - marketplacePath: ClientRequest__AbsolutePathBuf, + marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); export type ClientRequest__SandboxPolicy = @@ -9324,25 +9426,55 @@ export const ClientRequest__SkillsConfigWriteParams = Schema.Struct({ ), }); -export type ClientRequest__ExternalAgentConfigMigrationItem = { - readonly cwd?: string | null; - readonly description: string; - readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; +export type ClientRequest__SendAddCreditsNudgeEmailParams = { + readonly creditType: ClientRequest__AddCreditsNudgeCreditType; }; -export const ClientRequest__ExternalAgentConfigMigrationItem = Schema.Struct({ - cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", - }), - Schema.Null, - ]), - ), - description: Schema.String, - itemType: ClientRequest__ExternalAgentConfigMigrationItemType, +export const ClientRequest__SendAddCreditsNudgeEmailParams = Schema.Struct({ + creditType: ClientRequest__AddCreditsNudgeCreditType, }); +export type ClientRequest__DeviceKeyCreateParams = { + readonly accountUserId: string; + readonly clientId: string; + readonly protectionPolicy?: ClientRequest__DeviceKeyProtectionPolicy | null; +}; +export const ClientRequest__DeviceKeyCreateParams = Schema.Struct({ + accountUserId: Schema.String, + clientId: Schema.String, + protectionPolicy: Schema.optionalKey( + Schema.Union([ClientRequest__DeviceKeyProtectionPolicy, Schema.Null]).annotate({ + description: "Defaults to `hardware_only` when omitted.", + }), + ), +}).annotate({ description: "Create a controller-local device key with a random key id." }); + +export type ClientRequest__ContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: ClientRequest__ImageDetail | null; + readonly image_url: string; + readonly type: "input_image"; + } + | { readonly text: string; readonly type: "output_text" }; +export const ClientRequest__ContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), + }).annotate({ title: "InputTextContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + image_url: Schema.String, + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + }).annotate({ title: "InputImageContentItem" }), + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), + ], + { mode: "oneOf" }, +); + export type ClientRequest__FunctionCallOutputContentItem = | { readonly text: string; readonly type: "input_text" } | { @@ -9450,6 +9582,13 @@ export const ClientRequest__ConfigValueWriteParams = Schema.Struct({ value: Schema.Unknown, }); +export type ClientRequest__MigrationDetails = { + readonly plugins: ReadonlyArray; +}; +export const ClientRequest__MigrationDetails = Schema.Struct({ + plugins: Schema.Array(ClientRequest__PluginsMigration), +}); + export type ClientRequest__Settings = { readonly developer_instructions?: string | null; readonly model: string; @@ -9461,6 +9600,102 @@ export const ClientRequest__Settings = Schema.Struct({ reasoning_effort: Schema.optionalKey(Schema.Union([ClientRequest__ReasoningEffort, Schema.Null])), }).annotate({ description: "Settings for a collaboration mode." }); +export type ClientRequest__DeviceKeySignPayload = + | { + readonly accountUserId: string; + readonly audience: ClientRequest__RemoteControlClientConnectionAudience; + readonly clientId: string; + readonly nonce: string; + readonly scopes: ReadonlyArray; + readonly sessionId: string; + readonly targetOrigin: string; + readonly targetPath: string; + readonly tokenExpiresAt: number; + readonly tokenSha256Base64url: string; + readonly type: "remoteControlClientConnection"; + } + | { + readonly accountUserId: string; + readonly audience: ClientRequest__RemoteControlClientEnrollmentAudience; + readonly challengeExpiresAt: number; + readonly challengeId: string; + readonly clientId: string; + readonly deviceIdentitySha256Base64url: string; + readonly nonce: string; + readonly targetOrigin: string; + readonly targetPath: string; + readonly type: "remoteControlClientEnrollment"; + }; +export const ClientRequest__DeviceKeySignPayload = Schema.Union( + [ + Schema.Struct({ + accountUserId: Schema.String, + audience: ClientRequest__RemoteControlClientConnectionAudience, + clientId: Schema.String, + nonce: Schema.String, + scopes: Schema.Array(Schema.String).annotate({ + description: "Must contain exactly `remote_control_controller_websocket`.", + }), + sessionId: Schema.String.annotate({ + description: "Backend-issued websocket session id that this proof authorizes.", + }), + targetOrigin: Schema.String.annotate({ + description: + "Origin of the backend endpoint that issued the challenge and will verify this proof.", + }), + targetPath: Schema.String.annotate({ + description: "Websocket route path that this proof authorizes.", + }), + tokenExpiresAt: Schema.Number.annotate({ + description: "Remote-control token expiration as Unix seconds.", + format: "int64", + }).check(Schema.isInt()), + tokenSha256Base64url: Schema.String.annotate({ + description: + "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + }), + type: Schema.Literal("remoteControlClientConnection").annotate({ + title: "RemoteControlClientConnectionDeviceKeySignPayloadType", + }), + }).annotate({ + title: "RemoteControlClientConnectionDeviceKeySignPayload", + description: + "Payload bound to one remote-control controller websocket `/client` connection challenge.", + }), + Schema.Struct({ + accountUserId: Schema.String, + audience: ClientRequest__RemoteControlClientEnrollmentAudience, + challengeExpiresAt: Schema.Number.annotate({ + description: "Enrollment challenge expiration as Unix seconds.", + format: "int64", + }).check(Schema.isInt()), + challengeId: Schema.String.annotate({ + description: "Backend-issued enrollment challenge id that this proof authorizes.", + }), + clientId: Schema.String, + deviceIdentitySha256Base64url: Schema.String.annotate({ + description: + "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + }), + nonce: Schema.String, + targetOrigin: Schema.String.annotate({ + description: + "Origin of the backend endpoint that issued the challenge and will verify this proof.", + }), + targetPath: Schema.String.annotate({ + description: "HTTP route path that this proof authorizes.", + }), + type: Schema.Literal("remoteControlClientEnrollment").annotate({ + title: "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + }), + }).annotate({ + title: "RemoteControlClientEnrollmentDeviceKeySignPayload", + description: "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + }), + ], + { mode: "oneOf" }, +).annotate({ description: "Structured payloads accepted by `device/key/sign`." }); + export type ClientRequest__ReviewStartParams = { readonly delivery?: ClientRequest__ReviewDelivery | null; readonly target: ClientRequest__ReviewTarget; @@ -9599,6 +9834,37 @@ export const ClientRequest__SkillsListParams = Schema.Struct({ ), }); +export type ClientRequest__ThreadTurnsListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly sortDirection?: ClientRequest__SortDirection | null; + readonly threadId: string; +}; +export const ClientRequest__ThreadTurnsListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque cursor to pass to the next call to continue after the last turn.", + }), + Schema.Null, + ]), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), + sortDirection: Schema.optionalKey( + Schema.Union([ClientRequest__SortDirection, Schema.Null]).annotate({ + description: "Optional turn pagination direction; defaults to descending.", + }), + ), + threadId: Schema.String, +}); + export type ClientRequest__UserInput = | { readonly text: string; @@ -9664,6 +9930,7 @@ export type ClientRequest__ThreadListParams = { readonly limit?: number | null; readonly modelProviders?: ReadonlyArray | null; readonly searchTerm?: string | null; + readonly sortDirection?: ClientRequest__SortDirection | null; readonly sortKey?: ClientRequest__ThreadSortKey | null; readonly sourceKinds?: ReadonlyArray | null; }; @@ -9722,6 +9989,11 @@ export const ClientRequest__ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + sortDirection: Schema.optionalKey( + Schema.Union([ClientRequest__SortDirection, Schema.Null]).annotate({ + description: "Optional sort direction; defaults to descending (newest first).", + }), + ), sortKey: Schema.optionalKey( Schema.Union([ClientRequest__ThreadSortKey, Schema.Null]).annotate({ description: "Optional sort key; defaults to created_at.", @@ -9791,32 +10063,78 @@ export const ClientRequest__WindowsSandboxSetupStartParams = Schema.Struct({ mode: ClientRequest__WindowsSandboxSetupMode, }); -export type CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions = { - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; -}; -export const CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions = Schema.Struct( - { - read: Schema.optionalKey( - Schema.Union([ - Schema.Array(CommandExecutionRequestApprovalParams__AbsolutePathBuf), - Schema.Null, - ]), - ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(CommandExecutionRequestApprovalParams__AbsolutePathBuf), - Schema.Null, - ]), - ), - }, +export type CommandExecutionRequestApprovalParams__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: CommandExecutionRequestApprovalParams__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const CommandExecutionRequestApprovalParams__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: CommandExecutionRequestApprovalParams__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, ); -export type CommandExecutionRequestApprovalParams__NetworkApprovalContext = { - readonly host: string; - readonly protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol; -}; -export const CommandExecutionRequestApprovalParams__NetworkApprovalContext = Schema.Struct({ +export type CommandExecutionRequestApprovalParams__FileSystemPath = + | { readonly path: CommandExecutionRequestApprovalParams__AbsolutePathBuf; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: CommandExecutionRequestApprovalParams__FileSystemSpecialPath; + }; +export const CommandExecutionRequestApprovalParams__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: CommandExecutionRequestApprovalParams__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: CommandExecutionRequestApprovalParams__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); + +export type CommandExecutionRequestApprovalParams__NetworkApprovalContext = { + readonly host: string; + readonly protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol; +}; +export const CommandExecutionRequestApprovalParams__NetworkApprovalContext = Schema.Struct({ host: Schema.String, protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol, }); @@ -10085,31 +10403,97 @@ export const McpServerElicitationRequestParams__McpElicitationUntitledSingleSele type: McpServerElicitationRequestParams__McpElicitationStringType, }); -export type PermissionsRequestApprovalParams__AdditionalFileSystemPermissions = { - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; -}; -export const PermissionsRequestApprovalParams__AdditionalFileSystemPermissions = Schema.Struct({ - read: Schema.optionalKey( - Schema.Union([Schema.Array(PermissionsRequestApprovalParams__AbsolutePathBuf), Schema.Null]), - ), - write: Schema.optionalKey( - Schema.Union([Schema.Array(PermissionsRequestApprovalParams__AbsolutePathBuf), Schema.Null]), - ), -}); +export type PermissionsRequestApprovalParams__FileSystemPath = + | { readonly path: PermissionsRequestApprovalParams__AbsolutePathBuf; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: PermissionsRequestApprovalParams__FileSystemSpecialPath; + }; +export const PermissionsRequestApprovalParams__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: PermissionsRequestApprovalParams__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: PermissionsRequestApprovalParams__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); -export type PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = { - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; -}; -export const PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = Schema.Struct({ - read: Schema.optionalKey( - Schema.Union([Schema.Array(PermissionsRequestApprovalResponse__AbsolutePathBuf), Schema.Null]), - ), - write: Schema.optionalKey( - Schema.Union([Schema.Array(PermissionsRequestApprovalResponse__AbsolutePathBuf), Schema.Null]), - ), -}); +export type PermissionsRequestApprovalResponse__FileSystemPath = + | { readonly path: PermissionsRequestApprovalResponse__AbsolutePathBuf; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: PermissionsRequestApprovalResponse__FileSystemSpecialPath; + }; +export const PermissionsRequestApprovalResponse__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: PermissionsRequestApprovalResponse__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: PermissionsRequestApprovalResponse__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); + +export type ServerNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: ServerNotification__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const ServerNotification__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: ServerNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); export type ServerNotification__FsChangedNotification = { readonly changedPaths: ReadonlyArray; @@ -10164,6 +10548,28 @@ export const ServerNotification__CollabAgentState = Schema.Struct({ status: ServerNotification__CollabAgentStatus, }); +export type ServerNotification__FileSystemPath = + | { readonly path: ServerNotification__AbsolutePathBuf; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { readonly type: "special"; readonly value: ServerNotification__FileSystemSpecialPath }; +export const ServerNotification__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: ServerNotification__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: ServerNotification__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); + export type ServerNotification__FuzzyFileSearchResult = { readonly file_name: string; readonly indices?: ReadonlyArray | null; @@ -10207,7 +10613,7 @@ export const ServerNotification__GuardianApprovalReview = Schema.Struct({ ), }).annotate({ description: - "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", }); export type ServerNotification__HookOutputEntry = { @@ -10254,85 +10660,6 @@ export const ServerNotification__ModelReroutedNotification = Schema.Struct({ turnId: Schema.String, }); -export type ServerNotification__GuardianApprovalReviewAction = - | { - readonly command: string; - readonly cwd: string; - readonly source: ServerNotification__GuardianCommandSource; - readonly type: "command"; - } - | { - readonly argv: ReadonlyArray; - readonly cwd: string; - readonly program: string; - readonly source: ServerNotification__GuardianCommandSource; - readonly type: "execve"; - } - | { readonly cwd: string; readonly files: ReadonlyArray; readonly type: "applyPatch" } - | { - readonly host: string; - readonly port: number; - readonly protocol: ServerNotification__NetworkApprovalProtocol; - readonly target: string; - readonly type: "networkAccess"; - } - | { - readonly connectorId?: string | null; - readonly connectorName?: string | null; - readonly server: string; - readonly toolName: string; - readonly toolTitle?: string | null; - readonly type: "mcpToolCall"; - }; -export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - cwd: Schema.String, - source: ServerNotification__GuardianCommandSource, - type: Schema.Literal("command").annotate({ - title: "CommandGuardianApprovalReviewActionType", - }), - }).annotate({ title: "CommandGuardianApprovalReviewAction" }), - Schema.Struct({ - argv: Schema.Array(Schema.String), - cwd: Schema.String, - program: Schema.String, - source: ServerNotification__GuardianCommandSource, - type: Schema.Literal("execve").annotate({ title: "ExecveGuardianApprovalReviewActionType" }), - }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), - Schema.Struct({ - cwd: Schema.String, - files: Schema.Array(Schema.String), - type: Schema.Literal("applyPatch").annotate({ - title: "ApplyPatchGuardianApprovalReviewActionType", - }), - }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), - Schema.Struct({ - host: Schema.String, - port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - protocol: ServerNotification__NetworkApprovalProtocol, - target: Schema.String, - type: Schema.Literal("networkAccess").annotate({ - title: "NetworkAccessGuardianApprovalReviewActionType", - }), - }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), - Schema.Struct({ - connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - server: Schema.String, - toolName: Schema.String, - toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("mcpToolCall").annotate({ - title: "McpToolCallGuardianApprovalReviewActionType", - }), - }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), - ], - { mode: "oneOf" }, -); - export type ServerNotification__CodexErrorInfo = | "contextWindowExceeded" | "usageLimitExceeded" @@ -10463,6 +10790,7 @@ export type ServerNotification__RateLimitSnapshot = { readonly limitName?: string | null; readonly planType?: ServerNotification__PlanType | null; readonly primary?: ServerNotification__RateLimitWindow | null; + readonly rateLimitReachedType?: ServerNotification__RateLimitReachedType | null; readonly secondary?: ServerNotification__RateLimitWindow | null; }; export const ServerNotification__RateLimitSnapshot = Schema.Struct({ @@ -10471,6 +10799,9 @@ export const ServerNotification__RateLimitSnapshot = Schema.Struct({ limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), planType: Schema.optionalKey(Schema.Union([ServerNotification__PlanType, Schema.Null])), primary: Schema.optionalKey(Schema.Union([ServerNotification__RateLimitWindow, Schema.Null])), + rateLimitReachedType: Schema.optionalKey( + Schema.Union([ServerNotification__RateLimitReachedType, Schema.Null]), + ), secondary: Schema.optionalKey(Schema.Union([ServerNotification__RateLimitWindow, Schema.Null])), }); @@ -10647,18 +10978,47 @@ export const ServerNotification__WindowsSandboxSetupCompletedNotification = Sche success: Schema.Boolean, }); -export type ServerRequest__AdditionalFileSystemPermissions = { - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; -}; -export const ServerRequest__AdditionalFileSystemPermissions = Schema.Struct({ - read: Schema.optionalKey( - Schema.Union([Schema.Array(ServerRequest__AbsolutePathBuf), Schema.Null]), - ), - write: Schema.optionalKey( - Schema.Union([Schema.Array(ServerRequest__AbsolutePathBuf), Schema.Null]), - ), -}); +export type ServerRequest__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: ServerRequest__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const ServerRequest__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: ServerRequest__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); export type ServerRequest__ChatgptAuthTokensRefreshParams = { readonly previousAccountId?: string | null; @@ -10677,6 +11037,28 @@ export const ServerRequest__ChatgptAuthTokensRefreshParams = Schema.Struct({ reason: ServerRequest__ChatgptAuthTokensRefreshReason, }); +export type ServerRequest__FileSystemPath = + | { readonly path: ServerRequest__AbsolutePathBuf; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { readonly type: "special"; readonly value: ServerRequest__FileSystemSpecialPath }; +export const ServerRequest__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: ServerRequest__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: ServerRequest__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); + export type ServerRequest__McpElicitationBooleanSchema = { readonly default?: boolean | null; readonly description?: string | null; @@ -10949,6 +11331,7 @@ export type V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = { readonly limitName?: string | null; readonly planType?: V2AccountRateLimitsUpdatedNotification__PlanType | null; readonly primary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; + readonly rateLimitReachedType?: V2AccountRateLimitsUpdatedNotification__RateLimitReachedType | null; readonly secondary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; }; export const V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = Schema.Struct({ @@ -10963,6 +11346,9 @@ export const V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = Schema. primary: Schema.optionalKey( Schema.Union([V2AccountRateLimitsUpdatedNotification__RateLimitWindow, Schema.Null]), ), + rateLimitReachedType: Schema.optionalKey( + Schema.Union([V2AccountRateLimitsUpdatedNotification__RateLimitReachedType, Schema.Null]), + ), secondary: Schema.optionalKey( Schema.Union([V2AccountRateLimitsUpdatedNotification__RateLimitWindow, Schema.Null]), ), @@ -11346,22 +11732,118 @@ export const V2ConfigWriteResponse__ConfigLayerSource = Schema.Union( { mode: "oneOf" }, ); -export type V2ErrorNotification__CodexErrorInfo = - | "contextWindowExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "internalServerError" - | "unauthorized" - | "badRequest" - | "threadRollbackFailed" - | "sandboxError" - | "other" - | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } - | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } - | { readonly responseStreamDisconnected: { readonly httpStatusCode?: number | null } } - | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } - | { - readonly activeTurnNotSteerable: { +export type V2DeviceKeySignParams__DeviceKeySignPayload = + | { + readonly accountUserId: string; + readonly audience: V2DeviceKeySignParams__RemoteControlClientConnectionAudience; + readonly clientId: string; + readonly nonce: string; + readonly scopes: ReadonlyArray; + readonly sessionId: string; + readonly targetOrigin: string; + readonly targetPath: string; + readonly tokenExpiresAt: number; + readonly tokenSha256Base64url: string; + readonly type: "remoteControlClientConnection"; + } + | { + readonly accountUserId: string; + readonly audience: V2DeviceKeySignParams__RemoteControlClientEnrollmentAudience; + readonly challengeExpiresAt: number; + readonly challengeId: string; + readonly clientId: string; + readonly deviceIdentitySha256Base64url: string; + readonly nonce: string; + readonly targetOrigin: string; + readonly targetPath: string; + readonly type: "remoteControlClientEnrollment"; + }; +export const V2DeviceKeySignParams__DeviceKeySignPayload = Schema.Union( + [ + Schema.Struct({ + accountUserId: Schema.String, + audience: V2DeviceKeySignParams__RemoteControlClientConnectionAudience, + clientId: Schema.String, + nonce: Schema.String, + scopes: Schema.Array(Schema.String).annotate({ + description: "Must contain exactly `remote_control_controller_websocket`.", + }), + sessionId: Schema.String.annotate({ + description: "Backend-issued websocket session id that this proof authorizes.", + }), + targetOrigin: Schema.String.annotate({ + description: + "Origin of the backend endpoint that issued the challenge and will verify this proof.", + }), + targetPath: Schema.String.annotate({ + description: "Websocket route path that this proof authorizes.", + }), + tokenExpiresAt: Schema.Number.annotate({ + description: "Remote-control token expiration as Unix seconds.", + format: "int64", + }).check(Schema.isInt()), + tokenSha256Base64url: Schema.String.annotate({ + description: + "SHA-256 of the controller-scoped remote-control token, encoded as unpadded base64url.", + }), + type: Schema.Literal("remoteControlClientConnection").annotate({ + title: "RemoteControlClientConnectionDeviceKeySignPayloadType", + }), + }).annotate({ + title: "RemoteControlClientConnectionDeviceKeySignPayload", + description: + "Payload bound to one remote-control controller websocket `/client` connection challenge.", + }), + Schema.Struct({ + accountUserId: Schema.String, + audience: V2DeviceKeySignParams__RemoteControlClientEnrollmentAudience, + challengeExpiresAt: Schema.Number.annotate({ + description: "Enrollment challenge expiration as Unix seconds.", + format: "int64", + }).check(Schema.isInt()), + challengeId: Schema.String.annotate({ + description: "Backend-issued enrollment challenge id that this proof authorizes.", + }), + clientId: Schema.String, + deviceIdentitySha256Base64url: Schema.String.annotate({ + description: + "SHA-256 of the requested device identity operation, encoded as unpadded base64url.", + }), + nonce: Schema.String, + targetOrigin: Schema.String.annotate({ + description: + "Origin of the backend endpoint that issued the challenge and will verify this proof.", + }), + targetPath: Schema.String.annotate({ + description: "HTTP route path that this proof authorizes.", + }), + type: Schema.Literal("remoteControlClientEnrollment").annotate({ + title: "RemoteControlClientEnrollmentDeviceKeySignPayloadType", + }), + }).annotate({ + title: "RemoteControlClientEnrollmentDeviceKeySignPayload", + description: "Payload bound to a remote-control client `/client/enroll` ownership challenge.", + }), + ], + { mode: "oneOf" }, +).annotate({ description: "Structured payloads accepted by `device/key/sign`." }); + +export type V2ErrorNotification__CodexErrorInfo = + | "contextWindowExceeded" + | "usageLimitExceeded" + | "serverOverloaded" + | "internalServerError" + | "unauthorized" + | "badRequest" + | "threadRollbackFailed" + | "sandboxError" + | "other" + | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } + | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } + | { readonly responseStreamDisconnected: { readonly httpStatusCode?: number | null } } + | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } + | { + readonly activeTurnNotSteerable: { readonly turnKind: V2ErrorNotification__NonSteerableTurnKind; }; }; @@ -11452,42 +11934,29 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", }); -export type V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem = { - readonly cwd?: string | null; - readonly description: string; - readonly itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType; +export type V2ExternalAgentConfigDetectResponse__MigrationDetails = { + readonly plugins: ReadonlyArray; }; -export const V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem = Schema.Struct({ - cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", - }), - Schema.Null, - ]), - ), - description: Schema.String, - itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType, +export const V2ExternalAgentConfigDetectResponse__MigrationDetails = Schema.Struct({ + plugins: Schema.Array(V2ExternalAgentConfigDetectResponse__PluginsMigration), }); -export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem = { - readonly cwd?: string | null; - readonly description: string; - readonly itemType: V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType; +export type V2ExternalAgentConfigImportParams__MigrationDetails = { + readonly plugins: ReadonlyArray; }; -export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem = Schema.Struct({ - cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", - }), - Schema.Null, - ]), - ), - description: Schema.String, - itemType: V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType, +export const V2ExternalAgentConfigImportParams__MigrationDetails = Schema.Struct({ + plugins: Schema.Array(V2ExternalAgentConfigImportParams__PluginsMigration), +}); + +export type V2FileChangePatchUpdatedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2FileChangePatchUpdatedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2FileChangePatchUpdatedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2FileChangePatchUpdatedNotification__PatchChangeKind, + path: Schema.String, }); export type V2GetAccountRateLimitsResponse__RateLimitSnapshot = { @@ -11496,6 +11965,7 @@ export type V2GetAccountRateLimitsResponse__RateLimitSnapshot = { readonly limitName?: string | null; readonly planType?: V2GetAccountRateLimitsResponse__PlanType | null; readonly primary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; + readonly rateLimitReachedType?: V2GetAccountRateLimitsResponse__RateLimitReachedType | null; readonly secondary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; }; export const V2GetAccountRateLimitsResponse__RateLimitSnapshot = Schema.Struct({ @@ -11510,6 +11980,9 @@ export const V2GetAccountRateLimitsResponse__RateLimitSnapshot = Schema.Struct({ primary: Schema.optionalKey( Schema.Union([V2GetAccountRateLimitsResponse__RateLimitWindow, Schema.Null]), ), + rateLimitReachedType: Schema.optionalKey( + Schema.Union([V2GetAccountRateLimitsResponse__RateLimitReachedType, Schema.Null]), + ), secondary: Schema.optionalKey( Schema.Union([V2GetAccountRateLimitsResponse__RateLimitWindow, Schema.Null]), ), @@ -11554,6 +12027,48 @@ export const V2HookStartedNotification__HookOutputEntry = Schema.Struct({ text: Schema.String, }); +export type V2ItemCompletedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ItemCompletedNotification__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ItemCompletedNotification__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ItemCompletedNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ItemCompletedNotification__CollabAgentState = { readonly message?: string | null; readonly status: V2ItemCompletedNotification__CollabAgentStatus; @@ -11627,6 +12142,34 @@ export const V2ItemCompletedNotification__UserInput = Schema.Union( { mode: "oneOf" }, ); +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath = + | { + readonly path: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; + readonly type: "path"; + } + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath; + }; +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); + export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview = { readonly rationale?: string | null; readonly riskLevel?: V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel | null; @@ -11651,90 +12194,36 @@ export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApproval ), }).annotate({ description: - "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", }); -export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction = - | { - readonly command: string; - readonly cwd: string; - readonly source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource; - readonly type: "command"; - } - | { - readonly argv: ReadonlyArray; - readonly cwd: string; - readonly program: string; - readonly source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource; - readonly type: "execve"; - } - | { readonly cwd: string; readonly files: ReadonlyArray; readonly type: "applyPatch" } +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = | { - readonly host: string; - readonly port: number; - readonly protocol: V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol; - readonly target: string; - readonly type: "networkAccess"; + readonly path: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; + readonly type: "path"; } + | { readonly pattern: string; readonly type: "glob_pattern" } | { - readonly connectorId?: string | null; - readonly connectorName?: string | null; - readonly server: string; - readonly toolName: string; - readonly toolTitle?: string | null; - readonly type: "mcpToolCall"; + readonly type: "special"; + readonly value: V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath; }; -export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction = - Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - cwd: Schema.String, - source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource, - type: Schema.Literal("command").annotate({ - title: "CommandGuardianApprovalReviewActionType", - }), - }).annotate({ title: "CommandGuardianApprovalReviewAction" }), - Schema.Struct({ - argv: Schema.Array(Schema.String), - cwd: Schema.String, - program: Schema.String, - source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource, - type: Schema.Literal("execve").annotate({ - title: "ExecveGuardianApprovalReviewActionType", - }), - }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), - Schema.Struct({ - cwd: Schema.String, - files: Schema.Array(Schema.String), - type: Schema.Literal("applyPatch").annotate({ - title: "ApplyPatchGuardianApprovalReviewActionType", - }), - }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), - Schema.Struct({ - host: Schema.String, - port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - protocol: V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol, - target: Schema.String, - type: Schema.Literal("networkAccess").annotate({ - title: "NetworkAccessGuardianApprovalReviewActionType", - }), - }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), - Schema.Struct({ - connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - server: Schema.String, - toolName: Schema.String, - toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("mcpToolCall").annotate({ - title: "McpToolCallGuardianApprovalReviewActionType", - }), - }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), - ], - { mode: "oneOf" }, - ); +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = Schema.Union( + [ + Schema.Struct({ + path: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), + Schema.Struct({ + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), + Schema.Struct({ + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview = { readonly rationale?: string | null; @@ -11760,90 +12249,50 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRe ), }).annotate({ description: - "[UNSTABLE] Temporary guardian approval review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", }); -export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction = +export type V2ItemStartedNotification__CommandAction = | { readonly command: string; - readonly cwd: string; - readonly source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource; - readonly type: "command"; - } - | { - readonly argv: ReadonlyArray; - readonly cwd: string; - readonly program: string; - readonly source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource; - readonly type: "execve"; + readonly name: string; + readonly path: V2ItemStartedNotification__AbsolutePathBuf; + readonly type: "read"; } - | { readonly cwd: string; readonly files: ReadonlyArray; readonly type: "applyPatch" } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly host: string; - readonly port: number; - readonly protocol: V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol; - readonly target: string; - readonly type: "networkAccess"; + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; } - | { - readonly connectorId?: string | null; - readonly connectorName?: string | null; - readonly server: string; - readonly toolName: string; - readonly toolTitle?: string | null; - readonly type: "mcpToolCall"; - }; -export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction = - Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - cwd: Schema.String, - source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource, - type: Schema.Literal("command").annotate({ - title: "CommandGuardianApprovalReviewActionType", - }), - }).annotate({ title: "CommandGuardianApprovalReviewAction" }), - Schema.Struct({ - argv: Schema.Array(Schema.String), - cwd: Schema.String, - program: Schema.String, - source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource, - type: Schema.Literal("execve").annotate({ - title: "ExecveGuardianApprovalReviewActionType", - }), - }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), - Schema.Struct({ - cwd: Schema.String, - files: Schema.Array(Schema.String), - type: Schema.Literal("applyPatch").annotate({ - title: "ApplyPatchGuardianApprovalReviewActionType", - }), - }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), - Schema.Struct({ - host: Schema.String, - port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - protocol: V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol, - target: Schema.String, - type: Schema.Literal("networkAccess").annotate({ - title: "NetworkAccessGuardianApprovalReviewActionType", - }), - }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), - Schema.Struct({ - connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - server: Schema.String, - toolName: Schema.String, - toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("mcpToolCall").annotate({ - title: "McpToolCallGuardianApprovalReviewActionType", - }), - }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), - ], - { mode: "oneOf" }, - ); + | { readonly command: string; readonly type: "unknown" }; +export const V2ItemStartedNotification__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ItemStartedNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); export type V2ItemStartedNotification__CollabAgentState = { readonly message?: string | null; @@ -11956,12 +12405,15 @@ export type V2PluginListResponse__PluginInterface = { readonly capabilities: ReadonlyArray; readonly category?: string | null; readonly composerIcon?: V2PluginListResponse__AbsolutePathBuf | null; + readonly composerIconUrl?: string | null; readonly defaultPrompt?: ReadonlyArray | null; readonly developerName?: string | null; readonly displayName?: string | null; readonly logo?: V2PluginListResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; readonly longDescription?: string | null; readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; readonly screenshots: ReadonlyArray; readonly shortDescription?: string | null; readonly termsOfServiceUrl?: string | null; @@ -11972,7 +12424,15 @@ export const V2PluginListResponse__PluginInterface = Schema.Struct({ capabilities: Schema.Array(Schema.String), category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), composerIcon: Schema.optionalKey( - Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local composer icon path, resolved from the installed plugin package.", + }), + ), + composerIconUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote composer icon URL from the plugin catalog." }), + Schema.Null, + ]), ), defaultPrompt: Schema.optionalKey( Schema.Union([ @@ -11985,25 +12445,60 @@ export const V2PluginListResponse__PluginInterface = Schema.Struct({ ), developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logo: Schema.optionalKey(Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null])), + logo: Schema.optionalKey( + Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote logo URL from the plugin catalog." }), + Schema.Null, + ]), + ), longDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), privacyPolicyUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - screenshots: Schema.Array(V2PluginListResponse__AbsolutePathBuf), + screenshotUrls: Schema.Array(Schema.String).annotate({ + description: "Remote screenshot URLs from the plugin catalog.", + }), + screenshots: Schema.Array(V2PluginListResponse__AbsolutePathBuf).annotate({ + description: "Local screenshot paths, resolved from the installed plugin package.", + }), shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), termsOfServiceUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), websiteUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); -export type V2PluginListResponse__PluginSource = { - readonly path: V2PluginListResponse__AbsolutePathBuf; - readonly type: "local"; -}; +export type V2PluginListResponse__PluginSource = + | { readonly path: V2PluginListResponse__AbsolutePathBuf; readonly type: "local" } + | { + readonly path?: string | null; + readonly refName?: string | null; + readonly sha?: string | null; + readonly type: "git"; + readonly url: string; + } + | { readonly type: "remote" }; export const V2PluginListResponse__PluginSource = Schema.Union( [ Schema.Struct({ path: V2PluginListResponse__AbsolutePathBuf, type: Schema.Literal("local").annotate({ title: "LocalPluginSourceType" }), }).annotate({ title: "LocalPluginSource" }), + Schema.Struct({ + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + refName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("git").annotate({ title: "GitPluginSourceType" }), + url: Schema.String, + }).annotate({ title: "GitPluginSource" }), + Schema.Struct({ + type: Schema.Literal("remote").annotate({ title: "RemotePluginSourceType" }), + }).annotate({ + title: "RemotePluginSource", + description: + "The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.", + }), ], { mode: "oneOf" }, ); @@ -12013,12 +12508,15 @@ export type V2PluginReadResponse__PluginInterface = { readonly capabilities: ReadonlyArray; readonly category?: string | null; readonly composerIcon?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly composerIconUrl?: string | null; readonly defaultPrompt?: ReadonlyArray | null; readonly developerName?: string | null; readonly displayName?: string | null; readonly logo?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; readonly longDescription?: string | null; readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; readonly screenshots: ReadonlyArray; readonly shortDescription?: string | null; readonly termsOfServiceUrl?: string | null; @@ -12029,7 +12527,15 @@ export const V2PluginReadResponse__PluginInterface = Schema.Struct({ capabilities: Schema.Array(Schema.String), category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), composerIcon: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local composer icon path, resolved from the installed plugin package.", + }), + ), + composerIconUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote composer icon URL from the plugin catalog." }), + Schema.Null, + ]), ), defaultPrompt: Schema.optionalKey( Schema.Union([ @@ -12042,46 +12548,110 @@ export const V2PluginReadResponse__PluginInterface = Schema.Struct({ ), developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logo: Schema.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), + logo: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote logo URL from the plugin catalog." }), + Schema.Null, + ]), + ), longDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), privacyPolicyUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - screenshots: Schema.Array(V2PluginReadResponse__AbsolutePathBuf), + screenshotUrls: Schema.Array(Schema.String).annotate({ + description: "Remote screenshot URLs from the plugin catalog.", + }), + screenshots: Schema.Array(V2PluginReadResponse__AbsolutePathBuf).annotate({ + description: "Local screenshot paths, resolved from the installed plugin package.", + }), shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), termsOfServiceUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), websiteUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); -export type V2PluginReadResponse__PluginSource = { - readonly path: V2PluginReadResponse__AbsolutePathBuf; - readonly type: "local"; -}; +export type V2PluginReadResponse__PluginSource = + | { readonly path: V2PluginReadResponse__AbsolutePathBuf; readonly type: "local" } + | { + readonly path?: string | null; + readonly refName?: string | null; + readonly sha?: string | null; + readonly type: "git"; + readonly url: string; + } + | { readonly type: "remote" }; export const V2PluginReadResponse__PluginSource = Schema.Union( [ Schema.Struct({ path: V2PluginReadResponse__AbsolutePathBuf, type: Schema.Literal("local").annotate({ title: "LocalPluginSourceType" }), }).annotate({ title: "LocalPluginSource" }), + Schema.Struct({ + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + refName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("git").annotate({ title: "GitPluginSourceType" }), + url: Schema.String, + }).annotate({ title: "GitPluginSource" }), + Schema.Struct({ + type: Schema.Literal("remote").annotate({ title: "RemotePluginSourceType" }), + }).annotate({ + title: "RemotePluginSource", + description: + "The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.", + }), ], { mode: "oneOf" }, ); -export type V2PluginReadResponse__SkillSummary = { - readonly description: string; - readonly enabled: boolean; - readonly interface?: V2PluginReadResponse__SkillInterface | null; - readonly name: string; - readonly path: string; +export type V2PluginReadResponse__SkillInterface = { + readonly brandColor?: string | null; + readonly defaultPrompt?: string | null; + readonly displayName?: string | null; + readonly iconLarge?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly iconSmall?: V2PluginReadResponse__AbsolutePathBuf | null; readonly shortDescription?: string | null; }; -export const V2PluginReadResponse__SkillSummary = Schema.Struct({ - description: Schema.String, - enabled: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__SkillInterface, Schema.Null])), - name: Schema.String, - path: Schema.String, +export const V2PluginReadResponse__SkillInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + defaultPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconLarge: Schema.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), + iconSmall: Schema.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); +export type V2RawResponseItemCompletedNotification__ContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; + readonly image_url: string; + readonly type: "input_image"; + } + | { readonly text: string; readonly type: "output_text" }; +export const V2RawResponseItemCompletedNotification__ContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), + }).annotate({ title: "InputTextContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), + ), + image_url: Schema.String, + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + }).annotate({ title: "InputImageContentItem" }), + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), + ], + { mode: "oneOf" }, +); + export type V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem = | { readonly text: string; readonly type: "input_text" } | { @@ -12113,6 +12683,48 @@ export const V2RawResponseItemCompletedNotification__FunctionCallOutputContentIt "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", }); +export type V2ReviewStartResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ReviewStartResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ReviewStartResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ReviewStartResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ReviewStartResponse__CollabAgentState = { readonly message?: string | null; readonly status: V2ReviewStartResponse__CollabAgentStatus; @@ -12292,6 +12904,23 @@ export const V2ReviewStartResponse__UserInput = Schema.Union( { mode: "oneOf" }, ); +export type V2SkillsListResponse__SkillInterface = { + readonly brandColor?: string | null; + readonly defaultPrompt?: string | null; + readonly displayName?: string | null; + readonly iconLarge?: V2SkillsListResponse__AbsolutePathBuf | null; + readonly iconSmall?: V2SkillsListResponse__AbsolutePathBuf | null; + readonly shortDescription?: string | null; +}; +export const V2SkillsListResponse__SkillInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + defaultPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconLarge: Schema.optionalKey(Schema.Union([V2SkillsListResponse__AbsolutePathBuf, Schema.Null])), + iconSmall: Schema.optionalKey(Schema.Union([V2SkillsListResponse__AbsolutePathBuf, Schema.Null])), + shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}); + export type V2SkillsListResponse__SkillDependencies = { readonly tools: ReadonlyArray; }; @@ -12299,38 +12928,80 @@ export const V2SkillsListResponse__SkillDependencies = Schema.Struct({ tools: Schema.Array(V2SkillsListResponse__SkillToolDependency), }); -export type V2ThreadForkResponse__SandboxPolicy = - | { readonly type: "dangerFullAccess" } +export type V2ThreadForkResponse__CommandAction = | { - readonly access?: - | { - readonly includePlatformDefaults?: boolean; - readonly readableRoots?: ReadonlyArray; - readonly type: "restricted"; - } - | { readonly type: "fullAccess" }; - readonly networkAccess?: boolean; - readonly type: "readOnly"; + readonly command: string; + readonly name: string; + readonly path: V2ThreadForkResponse__AbsolutePathBuf; + readonly type: "read"; } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly readOnlyAccess?: - | { - readonly includePlatformDefaults?: boolean; - readonly readableRoots?: ReadonlyArray; - readonly type: "restricted"; - } - | { readonly type: "fullAccess" }; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2ThreadForkResponse__SandboxPolicy = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadForkResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadForkResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + +export type V2ThreadForkResponse__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { + readonly access?: + | { + readonly includePlatformDefaults?: boolean; + readonly readableRoots?: ReadonlyArray; + readonly type: "restricted"; + } + | { readonly type: "fullAccess" }; + readonly networkAccess?: boolean; + readonly type: "readOnly"; + } + | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly readOnlyAccess?: + | { + readonly includePlatformDefaults?: boolean; + readonly readableRoots?: ReadonlyArray; + readonly type: "restricted"; + } + | { readonly type: "fullAccess" }; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2ThreadForkResponse__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ title: "DangerFullAccessSandboxPolicyType", }), }).annotate({ title: "DangerFullAccessSandboxPolicy" }), @@ -12617,6 +13288,48 @@ export const V2ThreadForkResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadListResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadListResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadListResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadListResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadListResponse__CollabAgentState = { readonly message?: string | null; readonly status: V2ThreadListResponse__CollabAgentStatus; @@ -12829,6 +13542,48 @@ export const V2ThreadListResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadMetadataUpdateResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadMetadataUpdateResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadMetadataUpdateResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadMetadataUpdateResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadMetadataUpdateResponse__CollabAgentState = { readonly message?: string | null; readonly status: V2ThreadMetadataUpdateResponse__CollabAgentStatus; @@ -13041,6 +13796,48 @@ export const V2ThreadMetadataUpdateResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadReadResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadReadResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadReadResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadReadResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadReadResponse__CollabAgentState = { readonly message?: string | null; readonly status: V2ThreadReadResponse__CollabAgentStatus; @@ -13253,6 +14050,33 @@ export const V2ThreadReadResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadResumeParams__ContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadResumeParams__ImageDetail | null; + readonly image_url: string; + readonly type: "input_image"; + } + | { readonly text: string; readonly type: "output_text" }; +export const V2ThreadResumeParams__ContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), + }).annotate({ title: "InputTextContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), + image_url: Schema.String, + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + }).annotate({ title: "InputImageContentItem" }), + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadResumeParams__FunctionCallOutputContentItem = | { readonly text: string; readonly type: "input_text" } | { @@ -13282,6 +14106,48 @@ export const V2ThreadResumeParams__FunctionCallOutputContentItem = Schema.Union( "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", }); +export type V2ThreadResumeResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadResumeResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadResumeResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadResumeResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadResumeResponse__SandboxPolicy = | { readonly type: "dangerFullAccess" } | { @@ -13600,11 +14466,53 @@ export const V2ThreadResumeResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadRollbackResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadRollbackResponse__CollabAgentStatus; -}; -export const V2ThreadRollbackResponse__CollabAgentState = Schema.Struct({ +export type V2ThreadRollbackResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadRollbackResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadRollbackResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadRollbackResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + +export type V2ThreadRollbackResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadRollbackResponse__CollabAgentStatus; +}; +export const V2ThreadRollbackResponse__CollabAgentState = Schema.Struct({ message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: V2ThreadRollbackResponse__CollabAgentStatus, }); @@ -13812,6 +14720,48 @@ export const V2ThreadRollbackResponse__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadStartedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadStartedNotification__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadStartedNotification__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadStartedNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadStartedNotification__CollabAgentState = { readonly message?: string | null; readonly status: V2ThreadStartedNotification__CollabAgentStatus; @@ -14024,6 +14974,48 @@ export const V2ThreadStartedNotification__SubAgentSource = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadStartResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadStartResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadStartResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadStartResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + export type V2ThreadStartResponse__SandboxPolicy = | { readonly type: "dangerFullAccess" } | { @@ -14382,25 +15374,67 @@ export const V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage = Schema.St total: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown, }); -export type V2ThreadUnarchiveResponse__CollabAgentState = { +export type V2ThreadTurnsListResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadTurnsListResponse__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadTurnsListResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadTurnsListResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + +export type V2ThreadTurnsListResponse__CollabAgentState = { readonly message?: string | null; - readonly status: V2ThreadUnarchiveResponse__CollabAgentStatus; + readonly status: V2ThreadTurnsListResponse__CollabAgentStatus; }; -export const V2ThreadUnarchiveResponse__CollabAgentState = Schema.Struct({ +export const V2ThreadTurnsListResponse__CollabAgentState = Schema.Struct({ message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadUnarchiveResponse__CollabAgentStatus, + status: V2ThreadTurnsListResponse__CollabAgentStatus, }); -export type V2ThreadUnarchiveResponse__MemoryCitation = { - readonly entries: ReadonlyArray; +export type V2ThreadTurnsListResponse__MemoryCitation = { + readonly entries: ReadonlyArray; readonly threadIds: ReadonlyArray; }; -export const V2ThreadUnarchiveResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadUnarchiveResponse__MemoryCitationEntry), +export const V2ThreadTurnsListResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadTurnsListResponse__MemoryCitationEntry), threadIds: Schema.Array(Schema.String), }); -export type V2ThreadUnarchiveResponse__CodexErrorInfo = +export type V2ThreadTurnsListResponse__CodexErrorInfo = | "contextWindowExceeded" | "usageLimitExceeded" | "serverOverloaded" @@ -14416,10 +15450,10 @@ export type V2ThreadUnarchiveResponse__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind; + readonly turnKind: V2ThreadTurnsListResponse__NonSteerableTurnKind; }; }; -export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( +export const V2ThreadTurnsListResponse__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", @@ -14492,7 +15526,7 @@ export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( }), Schema.Struct({ activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind, + turnKind: V2ThreadTurnsListResponse__NonSteerableTurnKind, }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", @@ -14506,33 +15540,33 @@ export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", }); -export type V2ThreadUnarchiveResponse__FileUpdateChange = { +export type V2ThreadTurnsListResponse__FileUpdateChange = { readonly diff: string; - readonly kind: V2ThreadUnarchiveResponse__PatchChangeKind; + readonly kind: V2ThreadTurnsListResponse__PatchChangeKind; readonly path: string; }; -export const V2ThreadUnarchiveResponse__FileUpdateChange = Schema.Struct({ +export const V2ThreadTurnsListResponse__FileUpdateChange = Schema.Struct({ diff: Schema.String, - kind: V2ThreadUnarchiveResponse__PatchChangeKind, + kind: V2ThreadTurnsListResponse__PatchChangeKind, path: Schema.String, }); -export type V2ThreadUnarchiveResponse__UserInput = +export type V2ThreadTurnsListResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { readonly type: "image"; readonly url: string } | { readonly path: string; readonly type: "localImage" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadUnarchiveResponse__UserInput = Schema.Union( +export const V2ThreadTurnsListResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadUnarchiveResponse__TextElement).annotate({ + Schema.Array(V2ThreadTurnsListResponse__TextElement).annotate({ description: "UI-defined spans within `text` used to render or persist special elements.", default: [], }), @@ -14561,58 +15595,67 @@ export const V2ThreadUnarchiveResponse__UserInput = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadUnarchiveResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" +export type V2ThreadUnarchiveResponse__CommandAction = | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadUnarchiveResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadUnarchiveResponse__ThreadId; - }; + readonly command: string; + readonly name: string; + readonly path: V2ThreadUnarchiveResponse__AbsolutePathBuf; + readonly type: "read"; } - | { readonly other: string }; -export const V2ThreadUnarchiveResponse__SubAgentSource = Schema.Union( + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2ThreadUnarchiveResponse__CommandAction = Schema.Union( [ - Schema.Literals(["review", "compact", "memory_consolidation"]), Schema.Struct({ - thread_spawn: Schema.Struct({ - agent_nickname: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - agent_path: Schema.optionalKey( - Schema.Union([V2ThreadUnarchiveResponse__AgentPath, Schema.Null]), - ), - agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - depth: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - parent_thread_id: V2ThreadUnarchiveResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + command: Schema.String, + name: Schema.String, + path: V2ThreadUnarchiveResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), ], { mode: "oneOf" }, ); -export type V2TurnCompletedNotification__CollabAgentState = { +export type V2ThreadUnarchiveResponse__CollabAgentState = { readonly message?: string | null; - readonly status: V2TurnCompletedNotification__CollabAgentStatus; + readonly status: V2ThreadUnarchiveResponse__CollabAgentStatus; }; -export const V2TurnCompletedNotification__CollabAgentState = Schema.Struct({ +export const V2ThreadUnarchiveResponse__CollabAgentState = Schema.Struct({ message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2TurnCompletedNotification__CollabAgentStatus, + status: V2ThreadUnarchiveResponse__CollabAgentStatus, }); -export type V2TurnCompletedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; +export type V2ThreadUnarchiveResponse__MemoryCitation = { + readonly entries: ReadonlyArray; readonly threadIds: ReadonlyArray; }; -export const V2TurnCompletedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnCompletedNotification__MemoryCitationEntry), +export const V2ThreadUnarchiveResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadUnarchiveResponse__MemoryCitationEntry), threadIds: Schema.Array(Schema.String), }); -export type V2TurnCompletedNotification__CodexErrorInfo = +export type V2ThreadUnarchiveResponse__CodexErrorInfo = | "contextWindowExceeded" | "usageLimitExceeded" | "serverOverloaded" @@ -14628,10 +15671,10 @@ export type V2TurnCompletedNotification__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2TurnCompletedNotification__NonSteerableTurnKind; + readonly turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind; }; }; -export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( +export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", @@ -14704,7 +15747,7 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( }), Schema.Struct({ activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnCompletedNotification__NonSteerableTurnKind, + turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind, }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", @@ -14718,33 +15761,33 @@ export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", }); -export type V2TurnCompletedNotification__FileUpdateChange = { +export type V2ThreadUnarchiveResponse__FileUpdateChange = { readonly diff: string; - readonly kind: V2TurnCompletedNotification__PatchChangeKind; + readonly kind: V2ThreadUnarchiveResponse__PatchChangeKind; readonly path: string; }; -export const V2TurnCompletedNotification__FileUpdateChange = Schema.Struct({ +export const V2ThreadUnarchiveResponse__FileUpdateChange = Schema.Struct({ diff: Schema.String, - kind: V2TurnCompletedNotification__PatchChangeKind, + kind: V2ThreadUnarchiveResponse__PatchChangeKind, path: Schema.String, }); -export type V2TurnCompletedNotification__UserInput = +export type V2ThreadUnarchiveResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { readonly type: "image"; readonly url: string } | { readonly path: string; readonly type: "localImage" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2TurnCompletedNotification__UserInput = Schema.Union( +export const V2ThreadUnarchiveResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2TurnCompletedNotification__TextElement).annotate({ + Schema.Array(V2ThreadUnarchiveResponse__TextElement).annotate({ description: "UI-defined spans within `text` used to render or persist special elements.", default: [], }), @@ -14773,34 +15816,100 @@ export const V2TurnCompletedNotification__UserInput = Schema.Union( { mode: "oneOf" }, ); -export type V2TurnPlanUpdatedNotification__TurnPlanStep = { - readonly status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus; - readonly step: string; -}; -export const V2TurnPlanUpdatedNotification__TurnPlanStep = Schema.Struct({ - status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus, - step: Schema.String, -}); +export type V2ThreadUnarchiveResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadUnarchiveResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadUnarchiveResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadUnarchiveResponse__SubAgentSource = Schema.Union( + [ + Schema.Literals(["review", "compact", "memory_consolidation"]), + Schema.Struct({ + thread_spawn: Schema.Struct({ + agent_nickname: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + agent_path: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), + parent_thread_id: V2ThreadUnarchiveResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +); -export type V2TurnStartedNotification__CollabAgentState = { +export type V2TurnCompletedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2TurnCompletedNotification__AbsolutePathBuf; + readonly type: "read"; + } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2TurnCompletedNotification__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2TurnCompletedNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + +export type V2TurnCompletedNotification__CollabAgentState = { readonly message?: string | null; - readonly status: V2TurnStartedNotification__CollabAgentStatus; + readonly status: V2TurnCompletedNotification__CollabAgentStatus; }; -export const V2TurnStartedNotification__CollabAgentState = Schema.Struct({ +export const V2TurnCompletedNotification__CollabAgentState = Schema.Struct({ message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2TurnStartedNotification__CollabAgentStatus, + status: V2TurnCompletedNotification__CollabAgentStatus, }); -export type V2TurnStartedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; +export type V2TurnCompletedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; readonly threadIds: ReadonlyArray; }; -export const V2TurnStartedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnStartedNotification__MemoryCitationEntry), +export const V2TurnCompletedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2TurnCompletedNotification__MemoryCitationEntry), threadIds: Schema.Array(Schema.String), }); -export type V2TurnStartedNotification__CodexErrorInfo = +export type V2TurnCompletedNotification__CodexErrorInfo = | "contextWindowExceeded" | "usageLimitExceeded" | "serverOverloaded" @@ -14816,10 +15925,10 @@ export type V2TurnStartedNotification__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2TurnStartedNotification__NonSteerableTurnKind; + readonly turnKind: V2TurnCompletedNotification__NonSteerableTurnKind; }; }; -export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( +export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", @@ -14892,7 +16001,7 @@ export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( }), Schema.Struct({ activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnStartedNotification__NonSteerableTurnKind, + turnKind: V2TurnCompletedNotification__NonSteerableTurnKind, }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", @@ -14906,33 +16015,33 @@ export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", }); -export type V2TurnStartedNotification__FileUpdateChange = { +export type V2TurnCompletedNotification__FileUpdateChange = { readonly diff: string; - readonly kind: V2TurnStartedNotification__PatchChangeKind; + readonly kind: V2TurnCompletedNotification__PatchChangeKind; readonly path: string; }; -export const V2TurnStartedNotification__FileUpdateChange = Schema.Struct({ +export const V2TurnCompletedNotification__FileUpdateChange = Schema.Struct({ diff: Schema.String, - kind: V2TurnStartedNotification__PatchChangeKind, + kind: V2TurnCompletedNotification__PatchChangeKind, path: Schema.String, }); -export type V2TurnStartedNotification__UserInput = +export type V2TurnCompletedNotification__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { readonly type: "image"; readonly url: string } | { readonly path: string; readonly type: "localImage" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2TurnStartedNotification__UserInput = Schema.Union( +export const V2TurnCompletedNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2TurnStartedNotification__TextElement).annotate({ + Schema.Array(V2TurnCompletedNotification__TextElement).annotate({ description: "UI-defined spans within `text` used to render or persist special elements.", default: [], }), @@ -14961,188 +16070,76 @@ export const V2TurnStartedNotification__UserInput = Schema.Union( { mode: "oneOf" }, ); -export type V2TurnStartParams__SandboxPolicy = - | { readonly type: "dangerFullAccess" } +export type V2TurnPlanUpdatedNotification__TurnPlanStep = { + readonly status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus; + readonly step: string; +}; +export const V2TurnPlanUpdatedNotification__TurnPlanStep = Schema.Struct({ + status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus, + step: Schema.String, +}); + +export type V2TurnStartedNotification__CommandAction = | { - readonly access?: - | { - readonly includePlatformDefaults?: boolean; - readonly readableRoots?: ReadonlyArray; - readonly type: "restricted"; - } - | { readonly type: "fullAccess" }; - readonly networkAccess?: boolean; - readonly type: "readOnly"; + readonly command: string; + readonly name: string; + readonly path: V2TurnStartedNotification__AbsolutePathBuf; + readonly type: "read"; } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly readOnlyAccess?: - | { - readonly includePlatformDefaults?: boolean; - readonly readableRoots?: ReadonlyArray; - readonly type: "restricted"; - } - | { readonly type: "fullAccess" }; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2TurnStartParams__SandboxPolicy = Schema.Union( + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly command: string; readonly type: "unknown" }; +export const V2TurnStartedNotification__CommandAction = Schema.Union( [ Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + command: Schema.String, + name: Schema.String, + path: V2TurnStartedNotification__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), Schema.Struct({ - access: Schema.optionalKey( - Schema.Union( - [ - Schema.Struct({ - includePlatformDefaults: Schema.optionalKey( - Schema.Boolean.annotate({ default: true }), - ), - readableRoots: Schema.optionalKey( - Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), - ), - type: Schema.Literal("restricted").annotate({ - title: "RestrictedReadOnlyAccessType", - }), - }).annotate({ title: "RestrictedReadOnlyAccess" }), - Schema.Struct({ - type: Schema.Literal("fullAccess").annotate({ - title: "FullAccessReadOnlyAccessType", - }), - }).annotate({ title: "FullAccessReadOnlyAccess" }), - ], - { mode: "oneOf" }, - ).annotate({ default: { type: "fullAccess" } }), - ), - networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), - }).annotate({ title: "ReadOnlySandboxPolicy" }), + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), Schema.Struct({ - networkAccess: Schema.optionalKey( - Schema.Literals(["restricted", "enabled"]).annotate({ default: "restricted" }), - ), - type: Schema.Literal("externalSandbox").annotate({ - title: "ExternalSandboxSandboxPolicyType", - }), - }).annotate({ title: "ExternalSandboxSandboxPolicy" }), + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), Schema.Struct({ - excludeSlashTmp: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - excludeTmpdirEnvVar: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - readOnlyAccess: Schema.optionalKey( - Schema.Union( - [ - Schema.Struct({ - includePlatformDefaults: Schema.optionalKey( - Schema.Boolean.annotate({ default: true }), - ), - readableRoots: Schema.optionalKey( - Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), - ), - type: Schema.Literal("restricted").annotate({ - title: "RestrictedReadOnlyAccessType", - }), - }).annotate({ title: "RestrictedReadOnlyAccess" }), - Schema.Struct({ - type: Schema.Literal("fullAccess").annotate({ - title: "FullAccessReadOnlyAccessType", - }), - }).annotate({ title: "FullAccessReadOnlyAccess" }), - ], - { mode: "oneOf" }, - ).annotate({ default: { type: "fullAccess" } }), - ), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); - -export type V2TurnStartParams__Settings = { - readonly developer_instructions?: string | null; - readonly model: string; - readonly reasoning_effort?: V2TurnStartParams__ReasoningEffort | null; -}; -export const V2TurnStartParams__Settings = Schema.Struct({ - developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - model: Schema.String, - reasoning_effort: Schema.optionalKey( - Schema.Union([V2TurnStartParams__ReasoningEffort, Schema.Null]), - ), -}).annotate({ description: "Settings for a collaboration mode." }); - -export type V2TurnStartParams__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { readonly type: "image"; readonly url: string } - | { readonly path: string; readonly type: "localImage" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2TurnStartParams__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2TurnStartParams__TextElement).annotate({ - description: "UI-defined spans within `text` used to render or persist special elements.", - default: [], - }), - ), - type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), - }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - name: Schema.String, - path: Schema.String, - type: Schema.Literal("skill").annotate({ title: "SkillUserInputType" }), - }).annotate({ title: "SkillUserInput" }), - Schema.Struct({ - name: Schema.String, - path: Schema.String, - type: Schema.Literal("mention").annotate({ title: "MentionUserInputType" }), - }).annotate({ title: "MentionUserInput" }), + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), ], { mode: "oneOf" }, ); -export type V2TurnStartResponse__CollabAgentState = { +export type V2TurnStartedNotification__CollabAgentState = { readonly message?: string | null; - readonly status: V2TurnStartResponse__CollabAgentStatus; + readonly status: V2TurnStartedNotification__CollabAgentStatus; }; -export const V2TurnStartResponse__CollabAgentState = Schema.Struct({ +export const V2TurnStartedNotification__CollabAgentState = Schema.Struct({ message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2TurnStartResponse__CollabAgentStatus, + status: V2TurnStartedNotification__CollabAgentStatus, }); -export type V2TurnStartResponse__MemoryCitation = { - readonly entries: ReadonlyArray; +export type V2TurnStartedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; readonly threadIds: ReadonlyArray; }; -export const V2TurnStartResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnStartResponse__MemoryCitationEntry), +export const V2TurnStartedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2TurnStartedNotification__MemoryCitationEntry), threadIds: Schema.Array(Schema.String), }); -export type V2TurnStartResponse__CodexErrorInfo = +export type V2TurnStartedNotification__CodexErrorInfo = | "contextWindowExceeded" | "usageLimitExceeded" | "serverOverloaded" @@ -15158,10 +16155,10 @@ export type V2TurnStartResponse__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2TurnStartResponse__NonSteerableTurnKind; + readonly turnKind: V2TurnStartedNotification__NonSteerableTurnKind; }; }; -export const V2TurnStartResponse__CodexErrorInfo = Schema.Union( +export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", @@ -15234,7 +16231,7 @@ export const V2TurnStartResponse__CodexErrorInfo = Schema.Union( }), Schema.Struct({ activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnStartResponse__NonSteerableTurnKind, + turnKind: V2TurnStartedNotification__NonSteerableTurnKind, }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", @@ -15248,33 +16245,33 @@ export const V2TurnStartResponse__CodexErrorInfo = Schema.Union( "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", }); -export type V2TurnStartResponse__FileUpdateChange = { +export type V2TurnStartedNotification__FileUpdateChange = { readonly diff: string; - readonly kind: V2TurnStartResponse__PatchChangeKind; + readonly kind: V2TurnStartedNotification__PatchChangeKind; readonly path: string; }; -export const V2TurnStartResponse__FileUpdateChange = Schema.Struct({ +export const V2TurnStartedNotification__FileUpdateChange = Schema.Struct({ diff: Schema.String, - kind: V2TurnStartResponse__PatchChangeKind, + kind: V2TurnStartedNotification__PatchChangeKind, path: Schema.String, }); -export type V2TurnStartResponse__UserInput = +export type V2TurnStartedNotification__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { readonly type: "image"; readonly url: string } | { readonly path: string; readonly type: "localImage" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2TurnStartResponse__UserInput = Schema.Union( +export const V2TurnStartedNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2TurnStartResponse__TextElement).annotate({ + Schema.Array(V2TurnStartedNotification__TextElement).annotate({ description: "UI-defined spans within `text` used to render or persist special elements.", default: [], }), @@ -15303,22 +16300,141 @@ export const V2TurnStartResponse__UserInput = Schema.Union( { mode: "oneOf" }, ); -export type V2TurnSteerParams__UserInput = +export type V2TurnStartParams__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { + readonly access?: + | { + readonly includePlatformDefaults?: boolean; + readonly readableRoots?: ReadonlyArray; + readonly type: "restricted"; + } + | { readonly type: "fullAccess" }; + readonly networkAccess?: boolean; + readonly type: "readOnly"; + } + | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly readOnlyAccess?: + | { + readonly includePlatformDefaults?: boolean; + readonly readableRoots?: ReadonlyArray; + readonly type: "restricted"; + } + | { readonly type: "fullAccess" }; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2TurnStartParams__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + Schema.Struct({ + access: Schema.optionalKey( + Schema.Union( + [ + Schema.Struct({ + includePlatformDefaults: Schema.optionalKey( + Schema.Boolean.annotate({ default: true }), + ), + readableRoots: Schema.optionalKey( + Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), + ), + type: Schema.Literal("restricted").annotate({ + title: "RestrictedReadOnlyAccessType", + }), + }).annotate({ title: "RestrictedReadOnlyAccess" }), + Schema.Struct({ + type: Schema.Literal("fullAccess").annotate({ + title: "FullAccessReadOnlyAccessType", + }), + }).annotate({ title: "FullAccessReadOnlyAccess" }), + ], + { mode: "oneOf" }, + ).annotate({ default: { type: "fullAccess" } }), + ), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.Literals(["restricted", "enabled"]).annotate({ default: "restricted" }), + ), + type: Schema.Literal("externalSandbox").annotate({ + title: "ExternalSandboxSandboxPolicyType", + }), + }).annotate({ title: "ExternalSandboxSandboxPolicy" }), + Schema.Struct({ + excludeSlashTmp: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + excludeTmpdirEnvVar: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + readOnlyAccess: Schema.optionalKey( + Schema.Union( + [ + Schema.Struct({ + includePlatformDefaults: Schema.optionalKey( + Schema.Boolean.annotate({ default: true }), + ), + readableRoots: Schema.optionalKey( + Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), + ), + type: Schema.Literal("restricted").annotate({ + title: "RestrictedReadOnlyAccessType", + }), + }).annotate({ title: "RestrictedReadOnlyAccess" }), + Schema.Struct({ + type: Schema.Literal("fullAccess").annotate({ + title: "FullAccessReadOnlyAccessType", + }), + }).annotate({ title: "FullAccessReadOnlyAccess" }), + ], + { mode: "oneOf" }, + ).annotate({ default: { type: "fullAccess" } }), + ), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +); + +export type V2TurnStartParams__Settings = { + readonly developer_instructions?: string | null; + readonly model: string; + readonly reasoning_effort?: V2TurnStartParams__ReasoningEffort | null; +}; +export const V2TurnStartParams__Settings = Schema.Struct({ + developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasoning_effort: Schema.optionalKey( + Schema.Union([V2TurnStartParams__ReasoningEffort, Schema.Null]), + ), +}).annotate({ description: "Settings for a collaboration mode." }); + +export type V2TurnStartParams__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { readonly type: "image"; readonly url: string } | { readonly path: string; readonly type: "localImage" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2TurnSteerParams__UserInput = Schema.Union( +export const V2TurnStartParams__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2TurnSteerParams__TextElement).annotate({ + Schema.Array(V2TurnStartParams__TextElement).annotate({ description: "UI-defined spans within `text` used to render or persist special elements.", default: [], }), @@ -15347,148 +16463,413 @@ export const V2TurnSteerParams__UserInput = Schema.Union( { mode: "oneOf" }, ); -export type ApplyPatchApprovalResponse__ReviewDecision = - | "approved" +export type V2TurnStartResponse__CommandAction = | { - readonly approved_execpolicy_amendment: { - readonly proposed_execpolicy_amendment: ReadonlyArray; - }; + readonly command: string; + readonly name: string; + readonly path: V2TurnStartResponse__AbsolutePathBuf; + readonly type: "read"; } - | "approved_for_session" + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly network_policy_amendment: { - readonly network_policy_amendment: ApplyPatchApprovalResponse__NetworkPolicyAmendment; - }; + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; } - | "denied" - | "timed_out" - | "abort"; -export const ApplyPatchApprovalResponse__ReviewDecision = Schema.Union( + | { readonly command: string; readonly type: "unknown" }; +export const V2TurnStartResponse__CommandAction = Schema.Union( [ - Schema.Literal("approved").annotate({ - description: "User has approved this command and the agent should execute it.", - }), Schema.Struct({ - approved_execpolicy_amendment: Schema.Struct({ - proposed_execpolicy_amendment: Schema.Array(Schema.String), + command: Schema.String, + name: Schema.String, + path: V2TurnStartResponse__AbsolutePathBuf, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), + Schema.Struct({ + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("search").annotate({ title: "SearchCommandActionType" }), + }).annotate({ title: "SearchCommandAction" }), + Schema.Struct({ + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +); + +export type V2TurnStartResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2TurnStartResponse__CollabAgentStatus; +}; +export const V2TurnStartResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2TurnStartResponse__CollabAgentStatus, +}); + +export type V2TurnStartResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2TurnStartResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2TurnStartResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}); + +export type V2TurnStartResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "usageLimitExceeded" + | "serverOverloaded" + | "internalServerError" + | "unauthorized" + | "badRequest" + | "threadRollbackFailed" + | "sandboxError" + | "other" + | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } + | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } + | { readonly responseStreamDisconnected: { readonly httpStatusCode?: number | null } } + | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } + | { + readonly activeTurnNotSteerable: { + readonly turnKind: V2TurnStartResponse__NonSteerableTurnKind; + }; + }; +export const V2TurnStartResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "usageLimitExceeded", + "serverOverloaded", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), }), }).annotate({ - title: "ApprovedExecpolicyAmendmentReviewDecision", - description: - "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", - }), - Schema.Literal("approved_for_session").annotate({ - description: - "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.", + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", }), Schema.Struct({ - network_policy_amendment: Schema.Struct({ - network_policy_amendment: ApplyPatchApprovalResponse__NetworkPolicyAmendment, + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), }), }).annotate({ - title: "NetworkPolicyAmendmentReviewDecision", - description: - "User chose to persist a network policy rule (allow/deny) for future requests to the same host.", - }), - Schema.Literal("denied").annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", description: - "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "The response SSE stream disconnected in the middle of a turn before completion.", }), - Schema.Literal("timed_out").annotate({ - description: "Automatic approval review timed out before reaching a decision.", + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", }), - Schema.Literal("abort").annotate({ + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2TurnStartResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", description: - "User has denied this command and the agent should not do anything until the user's next command.", + "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", }), ], { mode: "oneOf" }, -).annotate({ description: "User's decision in response to an ExecApprovalRequest." }); +).annotate({ + description: + "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", +}); -export type ClientRequest__CommandExecParams = { - readonly command: ReadonlyArray; - readonly cwd?: string | null; - readonly disableOutputCap?: boolean; - readonly disableTimeout?: boolean; - readonly env?: { readonly [x: string]: string | null } | null; - readonly outputBytesCap?: number | null; - readonly processId?: string | null; - readonly sandboxPolicy?: ClientRequest__SandboxPolicy | null; - readonly size?: ClientRequest__CommandExecTerminalSize | null; - readonly streamStdin?: boolean; - readonly streamStdoutStderr?: boolean; - readonly timeoutMs?: number | null; - readonly tty?: boolean; +export type V2TurnStartResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2TurnStartResponse__PatchChangeKind; + readonly path: string; }; -export const ClientRequest__CommandExecParams = Schema.Struct({ - command: Schema.Array(Schema.String).annotate({ - description: "Command argv vector. Empty arrays are rejected.", - }), - cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional working directory. Defaults to the server cwd.", - }), - Schema.Null, - ]), - ), - disableOutputCap: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Disable stdout/stderr capture truncation for this request.\n\nCannot be combined with `outputBytesCap`.", - }), - ), - disableTimeout: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Disable the timeout entirely for this request.\n\nCannot be combined with `timeoutMs`.", - }), - ), - env: Schema.optionalKey( - Schema.Union([ - Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Null])).annotate({ - description: - "Optional environment overrides merged into the server-computed environment.\n\nMatching names override inherited values. Set a key to `null` to unset an inherited variable.", - }), - Schema.Null, - ]), - ), - outputBytesCap: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: - "Optional per-stream stdout/stderr capture cap in bytes.\n\nWhen omitted, the server default applies. Cannot be combined with `disableOutputCap`.", - format: "uint", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - processId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.", - }), - Schema.Null, - ]), - ), - sandboxPolicy: Schema.optionalKey( - Schema.Union([ClientRequest__SandboxPolicy, Schema.Null]).annotate({ - description: - "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted.", - }), - ), - size: Schema.optionalKey( - Schema.Union([ClientRequest__CommandExecTerminalSize, Schema.Null]).annotate({ - description: "Optional initial PTY size in character cells. Only valid when `tty` is true.", - }), - ), - streamStdin: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", +export const V2TurnStartResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2TurnStartResponse__PatchChangeKind, + path: Schema.String, +}); + +export type V2TurnStartResponse__UserInput = + | { + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; + } + | { readonly type: "image"; readonly url: string } + | { readonly path: string; readonly type: "localImage" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2TurnStartResponse__UserInput = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2TurnStartResponse__TextElement).annotate({ + description: "UI-defined spans within `text` used to render or persist special elements.", + default: [], + }), + ), + type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), + }).annotate({ title: "TextUserInput" }), + Schema.Struct({ + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "ImageUserInput" }), + Schema.Struct({ + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), + Schema.Struct({ + name: Schema.String, + path: Schema.String, + type: Schema.Literal("skill").annotate({ title: "SkillUserInputType" }), + }).annotate({ title: "SkillUserInput" }), + Schema.Struct({ + name: Schema.String, + path: Schema.String, + type: Schema.Literal("mention").annotate({ title: "MentionUserInputType" }), + }).annotate({ title: "MentionUserInput" }), + ], + { mode: "oneOf" }, +); + +export type V2TurnSteerParams__UserInput = + | { + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; + } + | { readonly type: "image"; readonly url: string } + | { readonly path: string; readonly type: "localImage" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2TurnSteerParams__UserInput = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2TurnSteerParams__TextElement).annotate({ + description: "UI-defined spans within `text` used to render or persist special elements.", + default: [], + }), + ), + type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), + }).annotate({ title: "TextUserInput" }), + Schema.Struct({ + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "ImageUserInput" }), + Schema.Struct({ + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), + Schema.Struct({ + name: Schema.String, + path: Schema.String, + type: Schema.Literal("skill").annotate({ title: "SkillUserInputType" }), + }).annotate({ title: "SkillUserInput" }), + Schema.Struct({ + name: Schema.String, + path: Schema.String, + type: Schema.Literal("mention").annotate({ title: "MentionUserInputType" }), + }).annotate({ title: "MentionUserInput" }), + ], + { mode: "oneOf" }, +); + +export type ApplyPatchApprovalResponse__ReviewDecision = + | "approved" + | { + readonly approved_execpolicy_amendment: { + readonly proposed_execpolicy_amendment: ReadonlyArray; + }; + } + | "approved_for_session" + | { + readonly network_policy_amendment: { + readonly network_policy_amendment: ApplyPatchApprovalResponse__NetworkPolicyAmendment; + }; + } + | "denied" + | "timed_out" + | "abort"; +export const ApplyPatchApprovalResponse__ReviewDecision = Schema.Union( + [ + Schema.Literal("approved").annotate({ + description: "User has approved this command and the agent should execute it.", + }), + Schema.Struct({ + approved_execpolicy_amendment: Schema.Struct({ + proposed_execpolicy_amendment: Schema.Array(Schema.String), + }), + }).annotate({ + title: "ApprovedExecpolicyAmendmentReviewDecision", + description: + "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + }), + Schema.Literal("approved_for_session").annotate({ + description: + "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.", + }), + Schema.Struct({ + network_policy_amendment: Schema.Struct({ + network_policy_amendment: ApplyPatchApprovalResponse__NetworkPolicyAmendment, + }), + }).annotate({ + title: "NetworkPolicyAmendmentReviewDecision", + description: + "User chose to persist a network policy rule (allow/deny) for future requests to the same host.", + }), + Schema.Literal("denied").annotate({ + description: + "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + }), + Schema.Literal("timed_out").annotate({ + description: "Automatic approval review timed out before reaching a decision.", + }), + Schema.Literal("abort").annotate({ + description: + "User has denied this command and the agent should not do anything until the user's next command.", + }), + ], + { mode: "oneOf" }, +).annotate({ description: "User's decision in response to an ExecApprovalRequest." }); + +export type ClientRequest__CommandExecParams = { + readonly command: ReadonlyArray; + readonly cwd?: string | null; + readonly disableOutputCap?: boolean; + readonly disableTimeout?: boolean; + readonly env?: { readonly [x: string]: string | null } | null; + readonly outputBytesCap?: number | null; + readonly processId?: string | null; + readonly sandboxPolicy?: ClientRequest__SandboxPolicy | null; + readonly size?: ClientRequest__CommandExecTerminalSize | null; + readonly streamStdin?: boolean; + readonly streamStdoutStderr?: boolean; + readonly timeoutMs?: number | null; + readonly tty?: boolean; +}; +export const ClientRequest__CommandExecParams = Schema.Struct({ + command: Schema.Array(Schema.String).annotate({ + description: "Command argv vector. Empty arrays are rejected.", + }), + cwd: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional working directory. Defaults to the server cwd.", + }), + Schema.Null, + ]), + ), + disableOutputCap: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Disable stdout/stderr capture truncation for this request.\n\nCannot be combined with `outputBytesCap`.", + }), + ), + disableTimeout: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Disable the timeout entirely for this request.\n\nCannot be combined with `timeoutMs`.", + }), + ), + env: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Null])).annotate({ + description: + "Optional environment overrides merged into the server-computed environment.\n\nMatching names override inherited values. Set a key to `null` to unset an inherited variable.", + }), + Schema.Null, + ]), + ), + outputBytesCap: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Optional per-stream stdout/stderr capture cap in bytes.\n\nWhen omitted, the server default applies. Cannot be combined with `disableOutputCap`.", + format: "uint", + }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), + processId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.", + }), + Schema.Null, + ]), + ), + sandboxPolicy: Schema.optionalKey( + Schema.Union([ClientRequest__SandboxPolicy, Schema.Null]).annotate({ + description: + "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted.", + }), + ), + size: Schema.optionalKey( + Schema.Union([ClientRequest__CommandExecTerminalSize, Schema.Null]).annotate({ + description: "Optional initial PTY size in character cells. Only valid when `tty` is true.", + }), + ), + streamStdin: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", }), ), streamStdoutStderr: Schema.optionalKey( @@ -15517,13 +16898,6 @@ export const ClientRequest__CommandExecParams = Schema.Struct({ "Run a standalone command (argv vector) in the server sandbox without creating a thread or turn.\n\nThe final `command/exec` response is deferred until the process exits and is sent only after all `command/exec/outputDelta` notifications for that connection have been emitted.", }); -export type ClientRequest__ExternalAgentConfigImportParams = { - readonly migrationItems: ReadonlyArray; -}; -export const ClientRequest__ExternalAgentConfigImportParams = Schema.Struct({ - migrationItems: Schema.Array(ClientRequest__ExternalAgentConfigMigrationItem), -}); - export type ClientRequest__FunctionCallOutputBody = | string | ReadonlyArray; @@ -15558,19 +16932,51 @@ export const ClientRequest__ConfigBatchWriteParams = Schema.Struct({ ), }); -export type ClientRequest__TurnStartParams = { - readonly approvalPolicy?: ClientRequest__AskForApproval | null; - readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; +export type ClientRequest__ExternalAgentConfigMigrationItem = { readonly cwd?: string | null; - readonly effort?: ClientRequest__ReasoningEffort | null; - readonly input: ReadonlyArray; - readonly model?: string | null; - readonly outputSchema?: unknown; - readonly personality?: ClientRequest__Personality | null; - readonly sandboxPolicy?: ClientRequest__SandboxPolicy | null; - readonly serviceTier?: ClientRequest__ServiceTier | null | null; - readonly summary?: ClientRequest__ReasoningSummary | null; - readonly threadId: string; + readonly description: string; + readonly details?: ClientRequest__MigrationDetails | null; + readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; +}; +export const ClientRequest__ExternalAgentConfigMigrationItem = Schema.Struct({ + cwd: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + }), + Schema.Null, + ]), + ), + description: Schema.String, + details: Schema.optionalKey(Schema.Union([ClientRequest__MigrationDetails, Schema.Null])), + itemType: ClientRequest__ExternalAgentConfigMigrationItemType, +}); + +export type ClientRequest__DeviceKeySignParams = { + readonly keyId: string; + readonly payload: ClientRequest__DeviceKeySignPayload; +}; +export const ClientRequest__DeviceKeySignParams = Schema.Struct({ + keyId: Schema.String, + payload: ClientRequest__DeviceKeySignPayload, +}).annotate({ + description: "Sign an accepted structured payload with a controller-local device key.", +}); + +export type ClientRequest__TurnStartParams = { + readonly approvalPolicy?: ClientRequest__AskForApproval | null; + readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; + readonly cwd?: string | null; + readonly effort?: ClientRequest__ReasoningEffort | null; + readonly input: ReadonlyArray; + readonly model?: string | null; + readonly outputSchema?: unknown; + readonly personality?: ClientRequest__Personality | null; + readonly sandboxPolicy?: ClientRequest__SandboxPolicy | null; + readonly serviceTier?: ClientRequest__ServiceTier | null | null; + readonly summary?: ClientRequest__ReasoningSummary | null; + readonly threadId: string; }; export const ClientRequest__TurnStartParams = Schema.Struct({ approvalPolicy: Schema.optionalKey( @@ -15649,6 +17055,15 @@ export const ClientRequest__TurnSteerParams = Schema.Struct({ threadId: Schema.String, }); +export type CommandExecutionRequestApprovalParams__FileSystemSandboxEntry = { + readonly access: CommandExecutionRequestApprovalParams__FileSystemAccessMode; + readonly path: CommandExecutionRequestApprovalParams__FileSystemPath; +}; +export const CommandExecutionRequestApprovalParams__FileSystemSandboxEntry = Schema.Struct({ + access: CommandExecutionRequestApprovalParams__FileSystemAccessMode, + path: CommandExecutionRequestApprovalParams__FileSystemPath, +}); + export type CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision = | "accept" | "acceptForSession" @@ -15835,33 +17250,22 @@ export const McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSc ], ); -export type PermissionsRequestApprovalParams__RequestPermissionProfile = { - readonly fileSystem?: PermissionsRequestApprovalParams__AdditionalFileSystemPermissions | null; - readonly network?: PermissionsRequestApprovalParams__AdditionalNetworkPermissions | null; +export type PermissionsRequestApprovalParams__FileSystemSandboxEntry = { + readonly access: PermissionsRequestApprovalParams__FileSystemAccessMode; + readonly path: PermissionsRequestApprovalParams__FileSystemPath; }; -export const PermissionsRequestApprovalParams__RequestPermissionProfile = Schema.Struct({ - fileSystem: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalParams__AdditionalFileSystemPermissions, Schema.Null]), - ), - network: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalParams__AdditionalNetworkPermissions, Schema.Null]), - ), +export const PermissionsRequestApprovalParams__FileSystemSandboxEntry = Schema.Struct({ + access: PermissionsRequestApprovalParams__FileSystemAccessMode, + path: PermissionsRequestApprovalParams__FileSystemPath, }); -export type PermissionsRequestApprovalResponse__GrantedPermissionProfile = { - readonly fileSystem?: PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions | null; - readonly network?: PermissionsRequestApprovalResponse__AdditionalNetworkPermissions | null; +export type PermissionsRequestApprovalResponse__FileSystemSandboxEntry = { + readonly access: PermissionsRequestApprovalResponse__FileSystemAccessMode; + readonly path: PermissionsRequestApprovalResponse__FileSystemPath; }; -export const PermissionsRequestApprovalResponse__GrantedPermissionProfile = Schema.Struct({ - fileSystem: Schema.optionalKey( - Schema.Union([ - PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions, - Schema.Null, - ]), - ), - network: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalResponse__AdditionalNetworkPermissions, Schema.Null]), - ), +export const PermissionsRequestApprovalResponse__FileSystemSandboxEntry = Schema.Struct({ + access: PermissionsRequestApprovalResponse__FileSystemAccessMode, + path: PermissionsRequestApprovalResponse__FileSystemPath, }); export type ServerNotification__AppInfo = { @@ -15903,6 +17307,15 @@ export const ServerNotification__AppInfo = Schema.Struct({ pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), }).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); +export type ServerNotification__FileSystemSandboxEntry = { + readonly access: ServerNotification__FileSystemAccessMode; + readonly path: ServerNotification__FileSystemPath; +}; +export const ServerNotification__FileSystemSandboxEntry = Schema.Struct({ + access: ServerNotification__FileSystemAccessMode, + path: ServerNotification__FileSystemPath, +}); + export type ServerNotification__FuzzyFileSearchSessionUpdatedNotification = { readonly files: ReadonlyArray; readonly query: string; @@ -15924,7 +17337,16 @@ export type ServerNotification__HookRunSummary = { readonly handlerType: ServerNotification__HookHandlerType; readonly id: string; readonly scope: ServerNotification__HookScope; - readonly sourcePath: string; + readonly source?: + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; + readonly sourcePath: ServerNotification__AbsolutePathBuf; readonly startedAt: number; readonly status: ServerNotification__HookRunStatus; readonly statusMessage?: string | null; @@ -15943,70 +17365,24 @@ export const ServerNotification__HookRunSummary = Schema.Struct({ handlerType: ServerNotification__HookHandlerType, id: Schema.String, scope: ServerNotification__HookScope, - sourcePath: Schema.String, + source: Schema.optionalKey( + Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", + ]).annotate({ default: "unknown" }), + ), + sourcePath: ServerNotification__AbsolutePathBuf, startedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), status: ServerNotification__HookRunStatus, statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); -export type ServerNotification__ItemGuardianApprovalReviewCompletedNotification = { - readonly action: ServerNotification__GuardianApprovalReviewAction; - readonly decisionSource: ServerNotification__AutoReviewDecisionSource; - readonly review: ServerNotification__GuardianApprovalReview; - readonly reviewId: string; - readonly targetItemId?: string | null; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ - action: ServerNotification__GuardianApprovalReviewAction, - decisionSource: ServerNotification__AutoReviewDecisionSource, - review: ServerNotification__GuardianApprovalReview, - reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), - targetItemId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", - }), - Schema.Null, - ]), - ), - threadId: Schema.String, - turnId: Schema.String, -}).annotate({ - description: - "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.", -}); - -export type ServerNotification__ItemGuardianApprovalReviewStartedNotification = { - readonly action: ServerNotification__GuardianApprovalReviewAction; - readonly review: ServerNotification__GuardianApprovalReview; - readonly reviewId: string; - readonly targetItemId?: string | null; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ - action: ServerNotification__GuardianApprovalReviewAction, - review: ServerNotification__GuardianApprovalReview, - reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), - targetItemId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", - }), - Schema.Null, - ]), - ), - threadId: Schema.String, - turnId: Schema.String, -}).annotate({ - description: - "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.", -}); - export type ServerNotification__TurnError = { readonly additionalDetails?: string | null; readonly codexErrorInfo?: ServerNotification__CodexErrorInfo | null; @@ -16020,6 +17396,19 @@ export const ServerNotification__TurnError = Schema.Struct({ message: Schema.String, }); +export type ServerNotification__FileChangePatchUpdatedNotification = { + readonly changes: ReadonlyArray; + readonly itemId: string; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__FileChangePatchUpdatedNotification = Schema.Struct({ + changes: Schema.Array(ServerNotification__FileUpdateChange), + itemId: Schema.String, + threadId: Schema.String, + turnId: Schema.String, +}); + export type ServerNotification__AccountRateLimitsUpdatedNotification = { readonly rateLimits: ServerNotification__RateLimitSnapshot; }; @@ -16076,6 +17465,7 @@ export type ServerNotification__ThreadItem = readonly durationMs?: number | null; readonly error?: ServerNotification__McpToolCallError | null; readonly id: string; + readonly mcpAppResourceUri?: string | null; readonly result?: ServerNotification__McpToolCallResult | null; readonly server: string; readonly status: ServerNotification__McpToolCallStatus; @@ -16087,6 +17477,7 @@ export type ServerNotification__ThreadItem = readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; + readonly namespace?: string | null; readonly status: ServerNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; @@ -16110,12 +17501,16 @@ export type ServerNotification__ThreadItem = readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: ServerNotification__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: ServerNotification__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } @@ -16172,7 +17567,10 @@ export const ServerNotification__ThreadItem = Schema.Union( description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -16232,6 +17630,7 @@ export const ServerNotification__ThreadItem = Schema.Union( ), error: Schema.optionalKey(Schema.Union([ServerNotification__McpToolCallError, Schema.Null])), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( Schema.Union([ServerNotification__McpToolCallResult, Schema.Null]), ), @@ -16258,6 +17657,7 @@ export const ServerNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: ServerNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, @@ -16318,14 +17718,16 @@ export const ServerNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: ServerNotification__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([ServerNotification__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -16415,17 +17817,13 @@ export const ServerNotification__TurnPlanUpdatedNotification = Schema.Struct({ turnId: Schema.String, }); -export type ServerRequest__RequestPermissionProfile = { - readonly fileSystem?: ServerRequest__AdditionalFileSystemPermissions | null; - readonly network?: ServerRequest__AdditionalNetworkPermissions | null; +export type ServerRequest__FileSystemSandboxEntry = { + readonly access: ServerRequest__FileSystemAccessMode; + readonly path: ServerRequest__FileSystemPath; }; -export const ServerRequest__RequestPermissionProfile = Schema.Struct({ - fileSystem: Schema.optionalKey( - Schema.Union([ServerRequest__AdditionalFileSystemPermissions, Schema.Null]), - ), - network: Schema.optionalKey( - Schema.Union([ServerRequest__AdditionalNetworkPermissions, Schema.Null]), - ), +export const ServerRequest__FileSystemSandboxEntry = Schema.Struct({ + access: ServerRequest__FileSystemAccessMode, + path: ServerRequest__FileSystemPath, }); export type ServerRequest__McpElicitationTitledMultiSelectEnumSchema = { @@ -16506,7 +17904,7 @@ export type ServerRequest__CommandExecutionRequestApprovalParams = { readonly approvalId?: string | null; readonly command?: string | null; readonly commandActions?: ReadonlyArray | null; - readonly cwd?: string | null; + readonly cwd?: ServerRequest__AbsolutePathBuf | null; readonly itemId: string; readonly networkApprovalContext?: ServerRequest__NetworkApprovalContext | null; readonly proposedExecpolicyAmendment?: ReadonlyArray | null; @@ -16540,10 +17938,9 @@ export const ServerRequest__CommandExecutionRequestApprovalParams = Schema.Struc ]), ), cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "The command's working directory." }), - Schema.Null, - ]), + Schema.Union([ServerRequest__AbsolutePathBuf, Schema.Null]).annotate({ + description: "The command's working directory.", + }), ), itemId: Schema.String, networkApprovalContext: Schema.optionalKey( @@ -16731,91 +18128,547 @@ export const V2ErrorNotification__TurnError = Schema.Struct({ message: Schema.String, }); -export type V2HookCompletedNotification__HookRunSummary = { - readonly completedAt?: number | null; - readonly displayOrder: number; - readonly durationMs?: number | null; - readonly entries: ReadonlyArray; - readonly eventName: V2HookCompletedNotification__HookEventName; - readonly executionMode: V2HookCompletedNotification__HookExecutionMode; - readonly handlerType: V2HookCompletedNotification__HookHandlerType; - readonly id: string; - readonly scope: V2HookCompletedNotification__HookScope; - readonly sourcePath: string; - readonly startedAt: number; - readonly status: V2HookCompletedNotification__HookRunStatus; - readonly statusMessage?: string | null; +export type V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem = { + readonly cwd?: string | null; + readonly description: string; + readonly details?: V2ExternalAgentConfigDetectResponse__MigrationDetails | null; + readonly itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType; +}; +export const V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem = Schema.Struct({ + cwd: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + }), + Schema.Null, + ]), + ), + description: Schema.String, + details: Schema.optionalKey( + Schema.Union([V2ExternalAgentConfigDetectResponse__MigrationDetails, Schema.Null]), + ), + itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType, +}); + +export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem = { + readonly cwd?: string | null; + readonly description: string; + readonly details?: V2ExternalAgentConfigImportParams__MigrationDetails | null; + readonly itemType: V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType; +}; +export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem = Schema.Struct({ + cwd: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + }), + Schema.Null, + ]), + ), + description: Schema.String, + details: Schema.optionalKey( + Schema.Union([V2ExternalAgentConfigImportParams__MigrationDetails, Schema.Null]), + ), + itemType: V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType, +}); + +export type V2HookCompletedNotification__HookRunSummary = { + readonly completedAt?: number | null; + readonly displayOrder: number; + readonly durationMs?: number | null; + readonly entries: ReadonlyArray; + readonly eventName: V2HookCompletedNotification__HookEventName; + readonly executionMode: V2HookCompletedNotification__HookExecutionMode; + readonly handlerType: V2HookCompletedNotification__HookHandlerType; + readonly id: string; + readonly scope: V2HookCompletedNotification__HookScope; + readonly source?: + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; + readonly sourcePath: V2HookCompletedNotification__AbsolutePathBuf; + readonly startedAt: number; + readonly status: V2HookCompletedNotification__HookRunStatus; + readonly statusMessage?: string | null; +}; +export const V2HookCompletedNotification__HookRunSummary = Schema.Struct({ + completedAt: Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + ), + displayOrder: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + durationMs: Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + ), + entries: Schema.Array(V2HookCompletedNotification__HookOutputEntry), + eventName: V2HookCompletedNotification__HookEventName, + executionMode: V2HookCompletedNotification__HookExecutionMode, + handlerType: V2HookCompletedNotification__HookHandlerType, + id: Schema.String, + scope: V2HookCompletedNotification__HookScope, + source: Schema.optionalKey( + Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", + ]).annotate({ default: "unknown" }), + ), + sourcePath: V2HookCompletedNotification__AbsolutePathBuf, + startedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + status: V2HookCompletedNotification__HookRunStatus, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}); + +export type V2HookStartedNotification__HookRunSummary = { + readonly completedAt?: number | null; + readonly displayOrder: number; + readonly durationMs?: number | null; + readonly entries: ReadonlyArray; + readonly eventName: V2HookStartedNotification__HookEventName; + readonly executionMode: V2HookStartedNotification__HookExecutionMode; + readonly handlerType: V2HookStartedNotification__HookHandlerType; + readonly id: string; + readonly scope: V2HookStartedNotification__HookScope; + readonly source?: + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; + readonly sourcePath: V2HookStartedNotification__AbsolutePathBuf; + readonly startedAt: number; + readonly status: V2HookStartedNotification__HookRunStatus; + readonly statusMessage?: string | null; +}; +export const V2HookStartedNotification__HookRunSummary = Schema.Struct({ + completedAt: Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + ), + displayOrder: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + durationMs: Schema.optionalKey( + Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + ), + entries: Schema.Array(V2HookStartedNotification__HookOutputEntry), + eventName: V2HookStartedNotification__HookEventName, + executionMode: V2HookStartedNotification__HookExecutionMode, + handlerType: V2HookStartedNotification__HookHandlerType, + id: Schema.String, + scope: V2HookStartedNotification__HookScope, + source: Schema.optionalKey( + Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", + ]).annotate({ default: "unknown" }), + ), + sourcePath: V2HookStartedNotification__AbsolutePathBuf, + startedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + status: V2HookStartedNotification__HookRunStatus, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}); + +export type V2ItemCompletedNotification__ThreadItem = + | { + readonly content: ReadonlyArray; + readonly id: string; + readonly type: "userMessage"; + } + | { + readonly fragments: ReadonlyArray; + readonly id: string; + readonly type: "hookPrompt"; + } + | { + readonly id: string; + readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; + readonly phase?: V2ItemCompletedNotification__MessagePhase | null; + readonly text: string; + readonly type: "agentMessage"; + } + | { readonly id: string; readonly text: string; readonly type: "plan" } + | { + readonly content?: ReadonlyArray; + readonly id: string; + readonly summary?: ReadonlyArray; + readonly type: "reasoning"; + } + | { + readonly aggregatedOutput?: string | null; + readonly command: string; + readonly commandActions: ReadonlyArray; + readonly cwd: string; + readonly durationMs?: number | null; + readonly exitCode?: number | null; + readonly id: string; + readonly processId?: string | null; + readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; + readonly status: V2ItemCompletedNotification__CommandExecutionStatus; + readonly type: "commandExecution"; + } + | { + readonly changes: ReadonlyArray; + readonly id: string; + readonly status: V2ItemCompletedNotification__PatchApplyStatus; + readonly type: "fileChange"; + } + | { + readonly arguments: unknown; + readonly durationMs?: number | null; + readonly error?: V2ItemCompletedNotification__McpToolCallError | null; + readonly id: string; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ItemCompletedNotification__McpToolCallResult | null; + readonly server: string; + readonly status: V2ItemCompletedNotification__McpToolCallStatus; + readonly tool: string; + readonly type: "mcpToolCall"; + } + | { + readonly arguments: unknown; + readonly contentItems?: ReadonlyArray | null; + readonly durationMs?: number | null; + readonly id: string; + readonly namespace?: string | null; + readonly status: V2ItemCompletedNotification__DynamicToolCallStatus; + readonly success?: boolean | null; + readonly tool: string; + readonly type: "dynamicToolCall"; + } + | { + readonly agentsStates: { + readonly [x: string]: V2ItemCompletedNotification__CollabAgentState; + }; + readonly id: string; + readonly model?: string | null; + readonly prompt?: string | null; + readonly reasoningEffort?: V2ItemCompletedNotification__ReasoningEffort | null; + readonly receiverThreadIds: ReadonlyArray; + readonly senderThreadId: string; + readonly status: "inProgress" | "completed" | "failed"; + readonly tool: "spawnAgent" | "sendInput" | "resumeAgent" | "wait" | "closeAgent"; + readonly type: "collabAgentToolCall"; + } + | { + readonly action?: V2ItemCompletedNotification__WebSearchAction | null; + readonly id: string; + readonly query: string; + readonly type: "webSearch"; + } + | { + readonly id: string; + readonly path: V2ItemCompletedNotification__AbsolutePathBuf; + readonly type: "imageView"; + } + | { + readonly id: string; + readonly result: string; + readonly revisedPrompt?: string | null; + readonly savedPath?: V2ItemCompletedNotification__AbsolutePathBuf | null; + readonly status: string; + readonly type: "imageGeneration"; + } + | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } + | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } + | { readonly id: string; readonly type: "contextCompaction" }; +export const V2ItemCompletedNotification__ThreadItem = Schema.Union( + [ + Schema.Struct({ + content: Schema.Array(V2ItemCompletedNotification__UserInput), + id: Schema.String, + type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), + }).annotate({ title: "UserMessageThreadItem" }), + Schema.Struct({ + fragments: Schema.Array(V2ItemCompletedNotification__HookPromptFragment), + id: Schema.String, + type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), + }).annotate({ title: "HookPromptThreadItem" }), + Schema.Struct({ + id: Schema.String, + memoryCitation: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), + ), + text: Schema.String, + type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), + }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + text: Schema.String, + type: Schema.Literal("plan").annotate({ title: "PlanThreadItemType" }), + }).annotate({ + title: "PlanThreadItem", + description: + "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + }), + Schema.Struct({ + content: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + id: Schema.String, + summary: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + type: Schema.Literal("reasoning").annotate({ title: "ReasoningThreadItemType" }), + }).annotate({ title: "ReasoningThreadItem" }), + Schema.Struct({ + aggregatedOutput: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "The command's output, aggregated from stdout and stderr.", + }), + Schema.Null, + ]), + ), + command: Schema.String.annotate({ description: "The command to be executed." }), + commandActions: Schema.Array(V2ItemCompletedNotification__CommandAction).annotate({ + description: + "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the command execution in milliseconds.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + exitCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The command's exit code.", + format: "int32", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + id: Schema.String, + processId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Identifier for the underlying PTY process (when available).", + }), + Schema.Null, + ]), + ), + source: Schema.optionalKey( + Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", + ]).annotate({ default: "agent" }), + ), + status: V2ItemCompletedNotification__CommandExecutionStatus, + type: Schema.Literal("commandExecution").annotate({ + title: "CommandExecutionThreadItemType", + }), + }).annotate({ title: "CommandExecutionThreadItem" }), + Schema.Struct({ + changes: Schema.Array(V2ItemCompletedNotification__FileUpdateChange), + id: Schema.String, + status: V2ItemCompletedNotification__PatchApplyStatus, + type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), + }).annotate({ title: "FileChangeThreadItem" }), + Schema.Struct({ + arguments: Schema.Unknown, + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the MCP tool call in milliseconds.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + error: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__McpToolCallError, Schema.Null]), + ), + id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + result: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__McpToolCallResult, Schema.Null]), + ), + server: Schema.String, + status: V2ItemCompletedNotification__McpToolCallStatus, + tool: Schema.String, + type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), + }).annotate({ title: "McpToolCallThreadItem" }), + Schema.Struct({ + arguments: Schema.Unknown, + contentItems: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemCompletedNotification__DynamicToolCallOutputContentItem), + Schema.Null, + ]), + ), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the dynamic tool call in milliseconds.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ItemCompletedNotification__DynamicToolCallStatus, + success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + tool: Schema.String, + type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), + }).annotate({ title: "DynamicToolCallThreadItem" }), + Schema.Struct({ + agentsStates: Schema.Record( + Schema.String, + V2ItemCompletedNotification__CollabAgentState, + ).annotate({ description: "Last known status of the target agents, when available." }), + id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Model requested for the spawned agent, when applicable.", + }), + Schema.Null, + ]), + ), + prompt: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Prompt text sent as part of the collab tool call, when available.", + }), + Schema.Null, + ]), + ), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ReasoningEffort, Schema.Null]).annotate({ + description: "Reasoning effort requested for the spawned agent, when applicable.", + }), + ), + receiverThreadIds: Schema.Array(Schema.String).annotate({ + description: + "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + }), + senderThreadId: Schema.String.annotate({ + description: "Thread ID of the agent issuing the collab request.", + }), + status: Schema.Literals(["inProgress", "completed", "failed"]).annotate({ + description: "Current status of the collab tool call.", + }), + tool: Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + ]).annotate({ description: "Name of the collab tool that was invoked." }), + type: Schema.Literal("collabAgentToolCall").annotate({ + title: "CollabAgentToolCallThreadItemType", + }), + }).annotate({ title: "CollabAgentToolCallThreadItem" }), + Schema.Struct({ + action: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__WebSearchAction, Schema.Null]), + ), + id: Schema.String, + query: Schema.String, + type: Schema.Literal("webSearch").annotate({ title: "WebSearchThreadItemType" }), + }).annotate({ title: "WebSearchThreadItem" }), + Schema.Struct({ + id: Schema.String, + path: V2ItemCompletedNotification__AbsolutePathBuf, + type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), + }).annotate({ title: "ImageViewThreadItem" }), + Schema.Struct({ + id: Schema.String, + result: Schema.String, + revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__AbsolutePathBuf, Schema.Null]), + ), + status: Schema.String, + type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), + }).annotate({ title: "ImageGenerationThreadItem" }), + Schema.Struct({ + id: Schema.String, + review: Schema.String, + type: Schema.Literal("enteredReviewMode").annotate({ + title: "EnteredReviewModeThreadItemType", + }), + }).annotate({ title: "EnteredReviewModeThreadItem" }), + Schema.Struct({ + id: Schema.String, + review: Schema.String, + type: Schema.Literal("exitedReviewMode").annotate({ + title: "ExitedReviewModeThreadItemType", + }), + }).annotate({ title: "ExitedReviewModeThreadItem" }), + Schema.Struct({ + id: Schema.String, + type: Schema.Literal("contextCompaction").annotate({ + title: "ContextCompactionThreadItemType", + }), + }).annotate({ title: "ContextCompactionThreadItem" }), + ], + { mode: "oneOf" }, +); + +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry = { + readonly access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode; + readonly path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath; }; -export const V2HookCompletedNotification__HookRunSummary = Schema.Struct({ - completedAt: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - displayOrder: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - durationMs: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - entries: Schema.Array(V2HookCompletedNotification__HookOutputEntry), - eventName: V2HookCompletedNotification__HookEventName, - executionMode: V2HookCompletedNotification__HookExecutionMode, - handlerType: V2HookCompletedNotification__HookHandlerType, - id: Schema.String, - scope: V2HookCompletedNotification__HookScope, - sourcePath: Schema.String, - startedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - status: V2HookCompletedNotification__HookRunStatus, - statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry = + Schema.Struct({ + access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode, + path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath, + }); -export type V2HookStartedNotification__HookRunSummary = { - readonly completedAt?: number | null; - readonly displayOrder: number; - readonly durationMs?: number | null; - readonly entries: ReadonlyArray; - readonly eventName: V2HookStartedNotification__HookEventName; - readonly executionMode: V2HookStartedNotification__HookExecutionMode; - readonly handlerType: V2HookStartedNotification__HookHandlerType; - readonly id: string; - readonly scope: V2HookStartedNotification__HookScope; - readonly sourcePath: string; - readonly startedAt: number; - readonly status: V2HookStartedNotification__HookRunStatus; - readonly statusMessage?: string | null; +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = { + readonly access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode; + readonly path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath; }; -export const V2HookStartedNotification__HookRunSummary = Schema.Struct({ - completedAt: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - displayOrder: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - durationMs: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - entries: Schema.Array(V2HookStartedNotification__HookOutputEntry), - eventName: V2HookStartedNotification__HookEventName, - executionMode: V2HookStartedNotification__HookExecutionMode, - handlerType: V2HookStartedNotification__HookHandlerType, - id: Schema.String, - scope: V2HookStartedNotification__HookScope, - sourcePath: Schema.String, - startedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - status: V2HookStartedNotification__HookRunStatus, - statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = + Schema.Struct({ + access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode, + path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath, + }); -export type V2ItemCompletedNotification__ThreadItem = +export type V2ItemStartedNotification__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; - readonly phase?: V2ItemCompletedNotification__MessagePhase | null; + readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; + readonly phase?: V2ItemStartedNotification__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -16829,51 +18682,51 @@ export type V2ItemCompletedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ItemCompletedNotification__CommandExecutionStatus; + readonly status: V2ItemStartedNotification__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ItemCompletedNotification__PatchApplyStatus; + readonly status: V2ItemStartedNotification__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ItemCompletedNotification__McpToolCallError | null; + readonly error?: V2ItemStartedNotification__McpToolCallError | null; readonly id: string; - readonly result?: V2ItemCompletedNotification__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ItemStartedNotification__McpToolCallResult | null; readonly server: string; - readonly status: V2ItemCompletedNotification__McpToolCallStatus; + readonly status: V2ItemStartedNotification__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ItemCompletedNotification__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ItemStartedNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { - readonly [x: string]: V2ItemCompletedNotification__CollabAgentState; - }; + readonly agentsStates: { readonly [x: string]: V2ItemStartedNotification__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ItemCompletedNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ItemStartedNotification__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -16881,42 +18734,46 @@ export type V2ItemCompletedNotification__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ItemCompletedNotification__WebSearchAction | null; + readonly action?: V2ItemStartedNotification__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ItemStartedNotification__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ItemStartedNotification__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ItemCompletedNotification__ThreadItem = Schema.Union( +export const V2ItemStartedNotification__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ItemCompletedNotification__UserInput), + content: Schema.Array(V2ItemStartedNotification__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ItemCompletedNotification__HookPromptFragment), + fragments: Schema.Array(V2ItemStartedNotification__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__MemoryCitation, Schema.Null]), + Schema.Union([V2ItemStartedNotification__MemoryCitation, Schema.Null]), ), phase: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), + Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), ), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), @@ -16946,11 +18803,14 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ItemCompletedNotification__CommandAction).annotate({ + commandActions: Schema.Array(V2ItemStartedNotification__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -16986,15 +18846,15 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ItemCompletedNotification__CommandExecutionStatus, + status: V2ItemStartedNotification__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ItemCompletedNotification__FileUpdateChange), + changes: Schema.Array(V2ItemStartedNotification__FileUpdateChange), id: Schema.String, - status: V2ItemCompletedNotification__PatchApplyStatus, + status: V2ItemStartedNotification__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -17009,14 +18869,15 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__McpToolCallError, Schema.Null]), + Schema.Union([V2ItemStartedNotification__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ItemStartedNotification__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ItemCompletedNotification__McpToolCallStatus, + status: V2ItemStartedNotification__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -17024,7 +18885,7 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemCompletedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ItemStartedNotification__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -17038,7 +18899,8 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ItemCompletedNotification__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ItemStartedNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), @@ -17046,7 +18908,7 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( Schema.Struct({ agentsStates: Schema.Record( Schema.String, - V2ItemCompletedNotification__CollabAgentState, + V2ItemStartedNotification__CollabAgentState, ).annotate({ description: "Last known status of the target agents, when available." }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( @@ -17066,7 +18928,7 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ItemStartedNotification__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -17093,7 +18955,7 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ItemStartedNotification__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -17101,14 +18963,16 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ItemStartedNotification__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -17136,21 +19000,140 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ItemStartedNotification__ThreadItem = +export type V2ModelListResponse__Model = { + readonly additionalSpeedTiers?: ReadonlyArray; + readonly availabilityNux?: V2ModelListResponse__ModelAvailabilityNux | null; + readonly defaultReasoningEffort: V2ModelListResponse__ReasoningEffort; + readonly description: string; + readonly displayName: string; + readonly hidden: boolean; + readonly id: string; + readonly inputModalities?: ReadonlyArray; + readonly isDefault: boolean; + readonly model: string; + readonly supportedReasoningEfforts: ReadonlyArray; + readonly supportsPersonality?: boolean; + readonly upgrade?: string | null; + readonly upgradeInfo?: V2ModelListResponse__ModelUpgradeInfo | null; +}; +export const V2ModelListResponse__Model = Schema.Struct({ + additionalSpeedTiers: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + availabilityNux: Schema.optionalKey( + Schema.Union([V2ModelListResponse__ModelAvailabilityNux, Schema.Null]), + ), + defaultReasoningEffort: V2ModelListResponse__ReasoningEffort, + description: Schema.String, + displayName: Schema.String, + hidden: Schema.Boolean, + id: Schema.String, + inputModalities: Schema.optionalKey( + Schema.Array(V2ModelListResponse__InputModality).annotate({ default: ["text", "image"] }), + ), + isDefault: Schema.Boolean, + model: Schema.String, + supportedReasoningEfforts: Schema.Array(V2ModelListResponse__ReasoningEffortOption), + supportsPersonality: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + upgrade: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + upgradeInfo: Schema.optionalKey( + Schema.Union([V2ModelListResponse__ModelUpgradeInfo, Schema.Null]), + ), +}); + +export type V2PluginListResponse__PluginSummary = { + readonly authPolicy: V2PluginListResponse__PluginAuthPolicy; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginListResponse__PluginInstallPolicy; + readonly installed: boolean; + readonly interface?: V2PluginListResponse__PluginInterface | null; + readonly name: string; + readonly source: V2PluginListResponse__PluginSource; +}; +export const V2PluginListResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginListResponse__PluginAuthPolicy, + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginListResponse__PluginInstallPolicy, + installed: Schema.Boolean, + interface: Schema.optionalKey(Schema.Union([V2PluginListResponse__PluginInterface, Schema.Null])), + name: Schema.String, + source: V2PluginListResponse__PluginSource, +}); + +export type V2PluginReadResponse__PluginSummary = { + readonly authPolicy: V2PluginReadResponse__PluginAuthPolicy; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginReadResponse__PluginInstallPolicy; + readonly installed: boolean; + readonly interface?: V2PluginReadResponse__PluginInterface | null; + readonly name: string; + readonly source: V2PluginReadResponse__PluginSource; +}; +export const V2PluginReadResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginReadResponse__PluginAuthPolicy, + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginReadResponse__PluginInstallPolicy, + installed: Schema.Boolean, + interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__PluginInterface, Schema.Null])), + name: Schema.String, + source: V2PluginReadResponse__PluginSource, +}); + +export type V2PluginReadResponse__SkillSummary = { + readonly description: string; + readonly enabled: boolean; + readonly interface?: V2PluginReadResponse__SkillInterface | null; + readonly name: string; + readonly path: V2PluginReadResponse__AbsolutePathBuf; + readonly shortDescription?: string | null; +}; +export const V2PluginReadResponse__SkillSummary = Schema.Struct({ + description: Schema.String, + enabled: Schema.Boolean, + interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__SkillInterface, Schema.Null])), + name: Schema.String, + path: V2PluginReadResponse__AbsolutePathBuf, + shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}); + +export type V2RawResponseItemCompletedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2RawResponseItemCompletedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem), +]); + +export type V2ReviewStartResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ReviewStartResponse__CodexErrorInfo | null; + readonly message: string; +}; +export const V2ReviewStartResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, +}); + +export type V2ReviewStartResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; - readonly phase?: V2ItemStartedNotification__MessagePhase | null; + readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; + readonly phase?: V2ReviewStartResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -17164,49 +19147,51 @@ export type V2ItemStartedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ItemStartedNotification__CommandExecutionStatus; + readonly status: V2ReviewStartResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ItemStartedNotification__PatchApplyStatus; + readonly status: V2ReviewStartResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ItemStartedNotification__McpToolCallError | null; + readonly error?: V2ReviewStartResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ItemStartedNotification__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ReviewStartResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ItemStartedNotification__McpToolCallStatus; + readonly status: V2ReviewStartResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ItemStartedNotification__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ReviewStartResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ItemStartedNotification__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ReviewStartResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ItemStartedNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ReviewStartResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -17214,43 +19199,45 @@ export type V2ItemStartedNotification__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ItemStartedNotification__WebSearchAction | null; + readonly action?: V2ReviewStartResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ReviewStartResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ReviewStartResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ItemStartedNotification__ThreadItem = Schema.Union( +export const V2ReviewStartResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ItemStartedNotification__UserInput), + content: Schema.Array(V2ReviewStartResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ItemStartedNotification__HookPromptFragment), + fragments: Schema.Array(V2ReviewStartResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__MemoryCitation, Schema.Null]), - ), - phase: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), + Schema.Union([V2ReviewStartResponse__MemoryCitation, Schema.Null]), ), + phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -17279,11 +19266,14 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ItemStartedNotification__CommandAction).annotate({ + commandActions: Schema.Array(V2ReviewStartResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -17319,15 +19309,15 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ItemStartedNotification__CommandExecutionStatus, + status: V2ReviewStartResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ItemStartedNotification__FileUpdateChange), + changes: Schema.Array(V2ReviewStartResponse__FileUpdateChange), id: Schema.String, - status: V2ItemStartedNotification__PatchApplyStatus, + status: V2ReviewStartResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -17342,14 +19332,15 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__McpToolCallError, Schema.Null]), + Schema.Union([V2ReviewStartResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ReviewStartResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ItemStartedNotification__McpToolCallStatus, + status: V2ReviewStartResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -17357,7 +19348,7 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemStartedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ReviewStartResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -17371,16 +19362,16 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ItemStartedNotification__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ReviewStartResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record( - Schema.String, - V2ItemStartedNotification__CollabAgentState, - ).annotate({ description: "Last known status of the target agents, when available." }), + agentsStates: Schema.Record(Schema.String, V2ReviewStartResponse__CollabAgentState).annotate({ + description: "Last known status of the target agents, when available.", + }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -17399,7 +19390,7 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ReviewStartResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -17426,7 +19417,7 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ReviewStartResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -17434,14 +19425,16 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ReviewStartResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -17469,123 +19462,65 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ModelListResponse__Model = { - readonly additionalSpeedTiers?: ReadonlyArray; - readonly availabilityNux?: V2ModelListResponse__ModelAvailabilityNux | null; - readonly defaultReasoningEffort: V2ModelListResponse__ReasoningEffort; +export type V2SkillsListResponse__SkillMetadata = { + readonly dependencies?: V2SkillsListResponse__SkillDependencies | null; readonly description: string; - readonly displayName: string; - readonly hidden: boolean; - readonly id: string; - readonly inputModalities?: ReadonlyArray; - readonly isDefault: boolean; - readonly model: string; - readonly supportedReasoningEfforts: ReadonlyArray; - readonly supportsPersonality?: boolean; - readonly upgrade?: string | null; - readonly upgradeInfo?: V2ModelListResponse__ModelUpgradeInfo | null; -}; -export const V2ModelListResponse__Model = Schema.Struct({ - additionalSpeedTiers: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), - availabilityNux: Schema.optionalKey( - Schema.Union([V2ModelListResponse__ModelAvailabilityNux, Schema.Null]), - ), - defaultReasoningEffort: V2ModelListResponse__ReasoningEffort, - description: Schema.String, - displayName: Schema.String, - hidden: Schema.Boolean, - id: Schema.String, - inputModalities: Schema.optionalKey( - Schema.Array(V2ModelListResponse__InputModality).annotate({ default: ["text", "image"] }), - ), - isDefault: Schema.Boolean, - model: Schema.String, - supportedReasoningEfforts: Schema.Array(V2ModelListResponse__ReasoningEffortOption), - supportsPersonality: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - upgrade: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - upgradeInfo: Schema.optionalKey( - Schema.Union([V2ModelListResponse__ModelUpgradeInfo, Schema.Null]), - ), -}); - -export type V2PluginListResponse__PluginSummary = { - readonly authPolicy: V2PluginListResponse__PluginAuthPolicy; - readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginListResponse__PluginInstallPolicy; - readonly installed: boolean; - readonly interface?: V2PluginListResponse__PluginInterface | null; - readonly name: string; - readonly source: V2PluginListResponse__PluginSource; -}; -export const V2PluginListResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginListResponse__PluginAuthPolicy, - enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginListResponse__PluginInstallPolicy, - installed: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2PluginListResponse__PluginInterface, Schema.Null])), - name: Schema.String, - source: V2PluginListResponse__PluginSource, -}); - -export type V2PluginReadResponse__PluginSummary = { - readonly authPolicy: V2PluginReadResponse__PluginAuthPolicy; readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginReadResponse__PluginInstallPolicy; - readonly installed: boolean; - readonly interface?: V2PluginReadResponse__PluginInterface | null; + readonly interface?: V2SkillsListResponse__SkillInterface | null; readonly name: string; - readonly source: V2PluginReadResponse__PluginSource; + readonly path: V2SkillsListResponse__AbsolutePathBuf; + readonly scope: V2SkillsListResponse__SkillScope; + readonly shortDescription?: string | null; }; -export const V2PluginReadResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginReadResponse__PluginAuthPolicy, +export const V2SkillsListResponse__SkillMetadata = Schema.Struct({ + dependencies: Schema.optionalKey( + Schema.Union([V2SkillsListResponse__SkillDependencies, Schema.Null]), + ), + description: Schema.String, enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginReadResponse__PluginInstallPolicy, - installed: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__PluginInterface, Schema.Null])), + interface: Schema.optionalKey(Schema.Union([V2SkillsListResponse__SkillInterface, Schema.Null])), name: Schema.String, - source: V2PluginReadResponse__PluginSource, + path: V2SkillsListResponse__AbsolutePathBuf, + scope: V2SkillsListResponse__SkillScope, + shortDescription: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + }), + Schema.Null, + ]), + ), }); -export type V2RawResponseItemCompletedNotification__FunctionCallOutputBody = - | string - | ReadonlyArray; -export const V2RawResponseItemCompletedNotification__FunctionCallOutputBody = Schema.Union([ - Schema.String, - Schema.Array(V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem), -]); - -export type V2ReviewStartResponse__TurnError = { +export type V2ThreadForkResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ReviewStartResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadForkResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ReviewStartResponse__TurnError = Schema.Struct({ +export const V2ThreadForkResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadForkResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ReviewStartResponse__ThreadItem = +export type V2ThreadForkResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; - readonly phase?: V2ReviewStartResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; + readonly phase?: V2ThreadForkResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -17599,49 +19534,51 @@ export type V2ReviewStartResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ReviewStartResponse__CommandExecutionStatus; + readonly status: V2ThreadForkResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ReviewStartResponse__PatchApplyStatus; + readonly status: V2ThreadForkResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ReviewStartResponse__McpToolCallError | null; + readonly error?: V2ThreadForkResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ReviewStartResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadForkResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ReviewStartResponse__McpToolCallStatus; + readonly status: V2ThreadForkResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ReviewStartResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadForkResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ReviewStartResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadForkResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ReviewStartResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -17649,41 +19586,45 @@ export type V2ReviewStartResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ReviewStartResponse__WebSearchAction | null; + readonly action?: V2ThreadForkResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadForkResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadForkResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ReviewStartResponse__ThreadItem = Schema.Union( +export const V2ThreadForkResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ReviewStartResponse__UserInput), + content: Schema.Array(V2ThreadForkResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ReviewStartResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadForkResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadForkResponse__MemoryCitation, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -17712,11 +19653,14 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ReviewStartResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadForkResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -17752,15 +19696,15 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ReviewStartResponse__CommandExecutionStatus, + status: V2ThreadForkResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ReviewStartResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadForkResponse__FileUpdateChange), id: Schema.String, - status: V2ReviewStartResponse__PatchApplyStatus, + status: V2ThreadForkResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -17775,14 +19719,15 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadForkResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadForkResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ReviewStartResponse__McpToolCallStatus, + status: V2ThreadForkResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -17790,7 +19735,7 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ReviewStartResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadForkResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -17804,13 +19749,14 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ReviewStartResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadForkResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ReviewStartResponse__CollabAgentState).annotate({ + agentsStates: Schema.Record(Schema.String, V2ThreadForkResponse__CollabAgentState).annotate({ description: "Last known status of the target agents, when available.", }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), @@ -17831,7 +19777,7 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadForkResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -17858,7 +19804,7 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadForkResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -17866,14 +19812,16 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadForkResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -17901,65 +19849,34 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2SkillsListResponse__SkillMetadata = { - readonly dependencies?: V2SkillsListResponse__SkillDependencies | null; - readonly description: string; - readonly enabled: boolean; - readonly interface?: V2SkillsListResponse__SkillInterface | null; - readonly name: string; - readonly path: string; - readonly scope: V2SkillsListResponse__SkillScope; - readonly shortDescription?: string | null; -}; -export const V2SkillsListResponse__SkillMetadata = Schema.Struct({ - dependencies: Schema.optionalKey( - Schema.Union([V2SkillsListResponse__SkillDependencies, Schema.Null]), - ), - description: Schema.String, - enabled: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2SkillsListResponse__SkillInterface, Schema.Null])), - name: Schema.String, - path: Schema.String, - scope: V2SkillsListResponse__SkillScope, - shortDescription: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", - }), - Schema.Null, - ]), - ), -}); - -export type V2ThreadForkResponse__TurnError = { +export type V2ThreadListResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadForkResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadListResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadForkResponse__TurnError = Schema.Struct({ +export const V2ThreadListResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadListResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadForkResponse__ThreadItem = +export type V2ThreadListResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; - readonly phase?: V2ThreadForkResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; + readonly phase?: V2ThreadListResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -17973,49 +19890,51 @@ export type V2ThreadForkResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadForkResponse__CommandExecutionStatus; + readonly status: V2ThreadListResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadForkResponse__PatchApplyStatus; + readonly status: V2ThreadListResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadForkResponse__McpToolCallError | null; + readonly error?: V2ThreadListResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadForkResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadListResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadForkResponse__McpToolCallStatus; + readonly status: V2ThreadListResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadForkResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadListResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadForkResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadListResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadListResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -18023,41 +19942,45 @@ export type V2ThreadForkResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadForkResponse__WebSearchAction | null; + readonly action?: V2ThreadListResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadListResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadListResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadForkResponse__ThreadItem = Schema.Union( +export const V2ThreadListResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadForkResponse__UserInput), + content: Schema.Array(V2ThreadListResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadForkResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadListResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadListResponse__MemoryCitation, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__MessagePhase, Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadListResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -18086,11 +20009,14 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadForkResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadListResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -18126,15 +20052,15 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadForkResponse__CommandExecutionStatus, + status: V2ThreadListResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadForkResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadListResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadForkResponse__PatchApplyStatus, + status: V2ThreadListResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -18149,14 +20075,15 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadListResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadListResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadForkResponse__McpToolCallStatus, + status: V2ThreadListResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -18164,7 +20091,7 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadForkResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadListResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -18178,13 +20105,14 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadForkResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadListResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ThreadForkResponse__CollabAgentState).annotate({ + agentsStates: Schema.Record(Schema.String, V2ThreadListResponse__CollabAgentState).annotate({ description: "Last known status of the target agents, when available.", }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), @@ -18205,7 +20133,7 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadListResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -18232,7 +20160,7 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadListResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -18240,14 +20168,16 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadListResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -18275,34 +20205,34 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadListResponse__TurnError = { +export type V2ThreadMetadataUpdateResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadListResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadMetadataUpdateResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadListResponse__TurnError = Schema.Struct({ +export const V2ThreadMetadataUpdateResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadListResponse__ThreadItem = +export type V2ThreadMetadataUpdateResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; - readonly phase?: V2ThreadListResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; + readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -18316,49 +20246,53 @@ export type V2ThreadListResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadListResponse__CommandExecutionStatus; + readonly status: V2ThreadMetadataUpdateResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadListResponse__PatchApplyStatus; + readonly status: V2ThreadMetadataUpdateResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadListResponse__McpToolCallError | null; + readonly error?: V2ThreadMetadataUpdateResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadListResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadMetadataUpdateResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadListResponse__McpToolCallStatus; + readonly status: V2ThreadMetadataUpdateResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadListResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadListResponse__CollabAgentState }; + readonly agentsStates: { + readonly [x: string]: V2ThreadMetadataUpdateResponse__CollabAgentState; + }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadListResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadMetadataUpdateResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -18366,41 +20300,47 @@ export type V2ThreadListResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadListResponse__WebSearchAction | null; + readonly action?: V2ThreadMetadataUpdateResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadMetadataUpdateResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadMetadataUpdateResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadListResponse__ThreadItem = Schema.Union( +export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadListResponse__UserInput), + content: Schema.Array(V2ThreadMetadataUpdateResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadListResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadMetadataUpdateResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__MessagePhase, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadListResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -18429,11 +20369,14 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadListResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadMetadataUpdateResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -18469,15 +20412,15 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadListResponse__CommandExecutionStatus, + status: V2ThreadMetadataUpdateResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadListResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadMetadataUpdateResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadListResponse__PatchApplyStatus, + status: V2ThreadMetadataUpdateResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -18492,14 +20435,15 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadListResponse__McpToolCallStatus, + status: V2ThreadMetadataUpdateResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -18507,7 +20451,7 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadListResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -18521,15 +20465,17 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadListResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ThreadListResponse__CollabAgentState).annotate({ - description: "Last known status of the target agents, when available.", - }), + agentsStates: Schema.Record( + Schema.String, + V2ThreadMetadataUpdateResponse__CollabAgentState, + ).annotate({ description: "Last known status of the target agents, when available." }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -18548,7 +20494,7 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadMetadataUpdateResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -18575,7 +20521,7 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -18583,14 +20529,16 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadMetadataUpdateResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -18618,34 +20566,34 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadMetadataUpdateResponse__TurnError = { +export type V2ThreadReadResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadMetadataUpdateResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadReadResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadMetadataUpdateResponse__TurnError = Schema.Struct({ +export const V2ThreadReadResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadReadResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadMetadataUpdateResponse__ThreadItem = +export type V2ThreadReadResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; - readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; + readonly phase?: V2ThreadReadResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -18659,51 +20607,51 @@ export type V2ThreadMetadataUpdateResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadMetadataUpdateResponse__CommandExecutionStatus; + readonly status: V2ThreadReadResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadMetadataUpdateResponse__PatchApplyStatus; + readonly status: V2ThreadReadResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadMetadataUpdateResponse__McpToolCallError | null; + readonly error?: V2ThreadReadResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadMetadataUpdateResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadReadResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadMetadataUpdateResponse__McpToolCallStatus; + readonly status: V2ThreadReadResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadReadResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { - readonly [x: string]: V2ThreadMetadataUpdateResponse__CollabAgentState; - }; + readonly agentsStates: { readonly [x: string]: V2ThreadReadResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadMetadataUpdateResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadReadResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -18711,43 +20659,45 @@ export type V2ThreadMetadataUpdateResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadMetadataUpdateResponse__WebSearchAction | null; + readonly action?: V2ThreadReadResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadReadResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadReadResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( +export const V2ThreadReadResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadMetadataUpdateResponse__UserInput), + content: Schema.Array(V2ThreadReadResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadMetadataUpdateResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadReadResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__MemoryCitation, Schema.Null]), - ), - phase: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__MessagePhase, Schema.Null]), + Schema.Union([V2ThreadReadResponse__MemoryCitation, Schema.Null]), ), + phase: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -18776,11 +20726,14 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadMetadataUpdateResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadReadResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -18816,15 +20769,15 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadMetadataUpdateResponse__CommandExecutionStatus, + status: V2ThreadReadResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadMetadataUpdateResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadReadResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadMetadataUpdateResponse__PatchApplyStatus, + status: V2ThreadReadResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -18839,14 +20792,15 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadReadResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadReadResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadMetadataUpdateResponse__McpToolCallStatus, + status: V2ThreadReadResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -18854,7 +20808,7 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadReadResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -18868,16 +20822,16 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadReadResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record( - Schema.String, - V2ThreadMetadataUpdateResponse__CollabAgentState, - ).annotate({ description: "Last known status of the target agents, when available." }), + agentsStates: Schema.Record(Schema.String, V2ThreadReadResponse__CollabAgentState).annotate({ + description: "Last known status of the target agents, when available.", + }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -18896,7 +20850,7 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadReadResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -18923,7 +20877,7 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadReadResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -18931,14 +20885,16 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadReadResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -18966,34 +20922,42 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadReadResponse__TurnError = { +export type V2ThreadResumeParams__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadResumeParams__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadResumeParams__FunctionCallOutputContentItem), +]); + +export type V2ThreadResumeResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadReadResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadResumeResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadReadResponse__TurnError = Schema.Struct({ +export const V2ThreadResumeResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadReadResponse__ThreadItem = +export type V2ThreadResumeResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; - readonly phase?: V2ThreadReadResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; + readonly phase?: V2ThreadResumeResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -19007,49 +20971,51 @@ export type V2ThreadReadResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadReadResponse__CommandExecutionStatus; + readonly status: V2ThreadResumeResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadReadResponse__PatchApplyStatus; + readonly status: V2ThreadResumeResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadReadResponse__McpToolCallError | null; + readonly error?: V2ThreadResumeResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadReadResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadResumeResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadReadResponse__McpToolCallStatus; + readonly status: V2ThreadResumeResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadReadResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadResumeResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadReadResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadResumeResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadReadResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -19057,41 +21023,45 @@ export type V2ThreadReadResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadReadResponse__WebSearchAction | null; + readonly action?: V2ThreadResumeResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadResumeResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadResumeResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadReadResponse__ThreadItem = Schema.Union( +export const V2ThreadResumeResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadReadResponse__UserInput), + content: Schema.Array(V2ThreadResumeResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadReadResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadResumeResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__MemoryCitation, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__MessagePhase, Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -19120,11 +21090,14 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadReadResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadResumeResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -19160,15 +21133,15 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadReadResponse__CommandExecutionStatus, + status: V2ThreadResumeResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadReadResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadResumeResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadReadResponse__PatchApplyStatus, + status: V2ThreadResumeResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -19183,14 +21156,15 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadReadResponse__McpToolCallStatus, + status: V2ThreadResumeResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -19198,7 +21172,7 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadReadResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadResumeResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -19212,15 +21186,16 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadReadResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadResumeResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ThreadReadResponse__CollabAgentState).annotate({ - description: "Last known status of the target agents, when available.", - }), + agentsStates: Schema.Record(Schema.String, V2ThreadResumeResponse__CollabAgentState).annotate( + { description: "Last known status of the target agents, when available." }, + ), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -19239,7 +21214,7 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -19266,7 +21241,7 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -19274,14 +21249,16 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadResumeResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -19309,42 +21286,34 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadResumeParams__FunctionCallOutputBody = - | string - | ReadonlyArray; -export const V2ThreadResumeParams__FunctionCallOutputBody = Schema.Union([ - Schema.String, - Schema.Array(V2ThreadResumeParams__FunctionCallOutputContentItem), -]); - -export type V2ThreadResumeResponse__TurnError = { +export type V2ThreadRollbackResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadResumeResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadRollbackResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadResumeResponse__TurnError = Schema.Struct({ +export const V2ThreadRollbackResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadRollbackResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadResumeResponse__ThreadItem = +export type V2ThreadRollbackResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; - readonly phase?: V2ThreadResumeResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadRollbackResponse__MemoryCitation | null; + readonly phase?: V2ThreadRollbackResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -19358,49 +21327,51 @@ export type V2ThreadResumeResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadResumeResponse__CommandExecutionStatus; + readonly status: V2ThreadRollbackResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadResumeResponse__PatchApplyStatus; + readonly status: V2ThreadRollbackResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadResumeResponse__McpToolCallError | null; + readonly error?: V2ThreadRollbackResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadResumeResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadRollbackResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadResumeResponse__McpToolCallStatus; + readonly status: V2ThreadRollbackResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadResumeResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadRollbackResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadResumeResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadRollbackResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadRollbackResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -19408,41 +21379,47 @@ export type V2ThreadResumeResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadResumeResponse__WebSearchAction | null; + readonly action?: V2ThreadRollbackResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadRollbackResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadRollbackResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadResumeResponse__ThreadItem = Schema.Union( +export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadResumeResponse__UserInput), + content: Schema.Array(V2ThreadRollbackResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadResumeResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadRollbackResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadRollbackResponse__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ThreadRollbackResponse__MessagePhase, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -19471,11 +21448,14 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadResumeResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadRollbackResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -19511,15 +21491,15 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadResumeResponse__CommandExecutionStatus, + status: V2ThreadRollbackResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadResumeResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadRollbackResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadResumeResponse__PatchApplyStatus, + status: V2ThreadRollbackResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -19534,14 +21514,15 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadRollbackResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadRollbackResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadResumeResponse__McpToolCallStatus, + status: V2ThreadRollbackResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -19549,7 +21530,7 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadResumeResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadRollbackResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -19563,15 +21544,17 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadResumeResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadRollbackResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ThreadResumeResponse__CollabAgentState).annotate( - { description: "Last known status of the target agents, when available." }, - ), + agentsStates: Schema.Record( + Schema.String, + V2ThreadRollbackResponse__CollabAgentState, + ).annotate({ description: "Last known status of the target agents, when available." }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -19590,7 +21573,7 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadRollbackResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -19617,7 +21600,7 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadRollbackResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -19625,14 +21608,16 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadRollbackResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadRollbackResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -19660,34 +21645,34 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadRollbackResponse__TurnError = { +export type V2ThreadStartedNotification__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadRollbackResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadStartedNotification__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadRollbackResponse__TurnError = Schema.Struct({ +export const V2ThreadStartedNotification__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadRollbackResponse__ThreadItem = +export type V2ThreadStartedNotification__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadRollbackResponse__MemoryCitation | null; - readonly phase?: V2ThreadRollbackResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; + readonly phase?: V2ThreadStartedNotification__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -19701,49 +21686,53 @@ export type V2ThreadRollbackResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadRollbackResponse__CommandExecutionStatus; + readonly status: V2ThreadStartedNotification__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadRollbackResponse__PatchApplyStatus; + readonly status: V2ThreadStartedNotification__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadRollbackResponse__McpToolCallError | null; + readonly error?: V2ThreadStartedNotification__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadRollbackResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadStartedNotification__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadRollbackResponse__McpToolCallStatus; + readonly status: V2ThreadStartedNotification__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadRollbackResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadStartedNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadRollbackResponse__CollabAgentState }; + readonly agentsStates: { + readonly [x: string]: V2ThreadStartedNotification__CollabAgentState; + }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadRollbackResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadStartedNotification__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -19751,42 +21740,46 @@ export type V2ThreadRollbackResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadRollbackResponse__WebSearchAction | null; + readonly action?: V2ThreadStartedNotification__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadStartedNotification__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadStartedNotification__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( +export const V2ThreadStartedNotification__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadRollbackResponse__UserInput), + content: Schema.Array(V2ThreadStartedNotification__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadRollbackResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadStartedNotification__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__MemoryCitation, Schema.Null]), ), phase: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__MessagePhase, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__MessagePhase, Schema.Null]), ), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), @@ -19816,11 +21809,14 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadRollbackResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadStartedNotification__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -19856,15 +21852,15 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadRollbackResponse__CommandExecutionStatus, + status: V2ThreadStartedNotification__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadRollbackResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadStartedNotification__FileUpdateChange), id: Schema.String, - status: V2ThreadRollbackResponse__PatchApplyStatus, + status: V2ThreadStartedNotification__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -19879,14 +21875,15 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadRollbackResponse__McpToolCallStatus, + status: V2ThreadStartedNotification__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -19894,7 +21891,7 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadRollbackResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadStartedNotification__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -19908,7 +21905,8 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadRollbackResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadStartedNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), @@ -19916,7 +21914,7 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( Schema.Struct({ agentsStates: Schema.Record( Schema.String, - V2ThreadRollbackResponse__CollabAgentState, + V2ThreadStartedNotification__CollabAgentState, ).annotate({ description: "Last known status of the target agents, when available." }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( @@ -19936,7 +21934,7 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadStartedNotification__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -19963,7 +21961,7 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -19971,14 +21969,16 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadStartedNotification__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -20006,34 +22006,34 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadStartedNotification__TurnError = { +export type V2ThreadStartResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadStartedNotification__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadStartResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadStartedNotification__TurnError = Schema.Struct({ +export const V2ThreadStartResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadStartResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadStartedNotification__ThreadItem = +export type V2ThreadStartResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; - readonly phase?: V2ThreadStartedNotification__MessagePhase | null; + readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; + readonly phase?: V2ThreadStartResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -20047,51 +22047,51 @@ export type V2ThreadStartedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadStartedNotification__CommandExecutionStatus; + readonly status: V2ThreadStartResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadStartedNotification__PatchApplyStatus; + readonly status: V2ThreadStartResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadStartedNotification__McpToolCallError | null; + readonly error?: V2ThreadStartResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadStartedNotification__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadStartResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadStartedNotification__McpToolCallStatus; + readonly status: V2ThreadStartResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadStartedNotification__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadStartResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { - readonly [x: string]: V2ThreadStartedNotification__CollabAgentState; - }; + readonly agentsStates: { readonly [x: string]: V2ThreadStartResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadStartedNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadStartResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -20099,43 +22099,45 @@ export type V2ThreadStartedNotification__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadStartedNotification__WebSearchAction | null; + readonly action?: V2ThreadStartResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadStartResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadStartResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadStartedNotification__ThreadItem = Schema.Union( +export const V2ThreadStartResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadStartedNotification__UserInput), + content: Schema.Array(V2ThreadStartResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadStartedNotification__HookPromptFragment), + fragments: Schema.Array(V2ThreadStartResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__MemoryCitation, Schema.Null]), - ), - phase: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__MessagePhase, Schema.Null]), + Schema.Union([V2ThreadStartResponse__MemoryCitation, Schema.Null]), ), + phase: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -20164,11 +22166,14 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadStartedNotification__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadStartResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -20204,15 +22209,15 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadStartedNotification__CommandExecutionStatus, + status: V2ThreadStartResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadStartedNotification__FileUpdateChange), + changes: Schema.Array(V2ThreadStartResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadStartedNotification__PatchApplyStatus, + status: V2ThreadStartResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -20227,14 +22232,15 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadStartResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadStartResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadStartedNotification__McpToolCallStatus, + status: V2ThreadStartResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -20242,7 +22248,7 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadStartedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadStartResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -20256,16 +22262,16 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadStartedNotification__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadStartResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record( - Schema.String, - V2ThreadStartedNotification__CollabAgentState, - ).annotate({ description: "Last known status of the target agents, when available." }), + agentsStates: Schema.Record(Schema.String, V2ThreadStartResponse__CollabAgentState).annotate({ + description: "Last known status of the target agents, when available.", + }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -20284,7 +22290,7 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadStartResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -20311,7 +22317,7 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadStartResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -20319,14 +22325,16 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadStartResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -20354,34 +22362,34 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( { mode: "oneOf" }, ); -export type V2ThreadStartResponse__TurnError = { +export type V2ThreadTurnsListResponse__TurnError = { readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadStartResponse__CodexErrorInfo | null; + readonly codexErrorInfo?: V2ThreadTurnsListResponse__CodexErrorInfo | null; readonly message: string; }; -export const V2ThreadStartResponse__TurnError = Schema.Struct({ +export const V2ThreadTurnsListResponse__TurnError = Schema.Struct({ additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__CodexErrorInfo, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__CodexErrorInfo, Schema.Null]), ), message: Schema.String, }); -export type V2ThreadStartResponse__ThreadItem = +export type V2ThreadTurnsListResponse__ThreadItem = | { - readonly content: ReadonlyArray; + readonly content: ReadonlyArray; readonly id: string; readonly type: "userMessage"; } | { - readonly fragments: ReadonlyArray; + readonly fragments: ReadonlyArray; readonly id: string; readonly type: "hookPrompt"; } | { readonly id: string; - readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; - readonly phase?: V2ThreadStartResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadTurnsListResponse__MemoryCitation | null; + readonly phase?: V2ThreadTurnsListResponse__MessagePhase | null; readonly text: string; readonly type: "agentMessage"; } @@ -20395,49 +22403,51 @@ export type V2ThreadStartResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; + readonly commandActions: ReadonlyArray; readonly cwd: string; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; readonly processId?: string | null; readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadStartResponse__CommandExecutionStatus; + readonly status: V2ThreadTurnsListResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadStartResponse__PatchApplyStatus; + readonly status: V2ThreadTurnsListResponse__PatchApplyStatus; readonly type: "fileChange"; } | { readonly arguments: unknown; readonly durationMs?: number | null; - readonly error?: V2ThreadStartResponse__McpToolCallError | null; + readonly error?: V2ThreadTurnsListResponse__McpToolCallError | null; readonly id: string; - readonly result?: V2ThreadStartResponse__McpToolCallResult | null; + readonly mcpAppResourceUri?: string | null; + readonly result?: V2ThreadTurnsListResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadStartResponse__McpToolCallStatus; + readonly status: V2ThreadTurnsListResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; - readonly status: V2ThreadStartResponse__DynamicToolCallStatus; + readonly namespace?: string | null; + readonly status: V2ThreadTurnsListResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadStartResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadTurnsListResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadStartResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadTurnsListResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; readonly status: "inProgress" | "completed" | "failed"; @@ -20445,41 +22455,47 @@ export type V2ThreadStartResponse__ThreadItem = readonly type: "collabAgentToolCall"; } | { - readonly action?: V2ThreadStartResponse__WebSearchAction | null; + readonly action?: V2ThreadTurnsListResponse__WebSearchAction | null; readonly id: string; readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadTurnsListResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadTurnsListResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } | { readonly id: string; readonly review: string; readonly type: "exitedReviewMode" } | { readonly id: string; readonly type: "contextCompaction" }; -export const V2ThreadStartResponse__ThreadItem = Schema.Union( +export const V2ThreadTurnsListResponse__ThreadItem = Schema.Union( [ Schema.Struct({ - content: Schema.Array(V2ThreadStartResponse__UserInput), + content: Schema.Array(V2ThreadTurnsListResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadStartResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadTurnsListResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__MessagePhase, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__MessagePhase, Schema.Null])), text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), @@ -20508,11 +22524,14 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(V2ThreadStartResponse__CommandAction).annotate({ + commandActions: Schema.Array(V2ThreadTurnsListResponse__CommandAction).annotate({ description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -20548,15 +22567,15 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( "unifiedExecInteraction", ]).annotate({ default: "agent" }), ), - status: V2ThreadStartResponse__CommandExecutionStatus, + status: V2ThreadTurnsListResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadStartResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadTurnsListResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadStartResponse__PatchApplyStatus, + status: V2ThreadTurnsListResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ @@ -20571,14 +22590,15 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadStartResponse__McpToolCallStatus, + status: V2ThreadTurnsListResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), @@ -20586,7 +22606,7 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( arguments: Schema.Unknown, contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadStartResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -20600,15 +22620,17 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, - status: V2ThreadStartResponse__DynamicToolCallStatus, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadTurnsListResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), }).annotate({ title: "DynamicToolCallThreadItem" }), Schema.Struct({ - agentsStates: Schema.Record(Schema.String, V2ThreadStartResponse__CollabAgentState).annotate({ - description: "Last known status of the target agents, when available.", - }), + agentsStates: Schema.Record( + Schema.String, + V2ThreadTurnsListResponse__CollabAgentState, + ).annotate({ description: "Last known status of the target agents, when available." }), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), model: Schema.optionalKey( Schema.Union([ @@ -20627,7 +22649,7 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadTurnsListResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -20654,7 +22676,7 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, @@ -20662,14 +22684,16 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadTurnsListResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -20759,6 +22783,7 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly durationMs?: number | null; readonly error?: V2ThreadUnarchiveResponse__McpToolCallError | null; readonly id: string; + readonly mcpAppResourceUri?: string | null; readonly result?: V2ThreadUnarchiveResponse__McpToolCallResult | null; readonly server: string; readonly status: V2ThreadUnarchiveResponse__McpToolCallStatus; @@ -20770,6 +22795,7 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; + readonly namespace?: string | null; readonly status: V2ThreadUnarchiveResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; @@ -20793,12 +22819,16 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2ThreadUnarchiveResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2ThreadUnarchiveResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } @@ -20857,7 +22887,10 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -20919,6 +22952,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( Schema.Union([V2ThreadUnarchiveResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__McpToolCallResult, Schema.Null]), ), @@ -20945,6 +22979,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: V2ThreadUnarchiveResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, @@ -21008,14 +23043,16 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2ThreadUnarchiveResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -21105,6 +23142,7 @@ export type V2TurnCompletedNotification__ThreadItem = readonly durationMs?: number | null; readonly error?: V2TurnCompletedNotification__McpToolCallError | null; readonly id: string; + readonly mcpAppResourceUri?: string | null; readonly result?: V2TurnCompletedNotification__McpToolCallResult | null; readonly server: string; readonly status: V2TurnCompletedNotification__McpToolCallStatus; @@ -21116,6 +23154,7 @@ export type V2TurnCompletedNotification__ThreadItem = readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; + readonly namespace?: string | null; readonly status: V2TurnCompletedNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; @@ -21141,12 +23180,16 @@ export type V2TurnCompletedNotification__ThreadItem = readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2TurnCompletedNotification__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2TurnCompletedNotification__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } @@ -21205,7 +23248,10 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -21267,6 +23313,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( Schema.Union([V2TurnCompletedNotification__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnCompletedNotification__McpToolCallResult, Schema.Null]), ), @@ -21293,6 +23340,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: V2TurnCompletedNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, @@ -21356,14 +23404,16 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2TurnCompletedNotification__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -21453,6 +23503,7 @@ export type V2TurnStartedNotification__ThreadItem = readonly durationMs?: number | null; readonly error?: V2TurnStartedNotification__McpToolCallError | null; readonly id: string; + readonly mcpAppResourceUri?: string | null; readonly result?: V2TurnStartedNotification__McpToolCallResult | null; readonly server: string; readonly status: V2TurnStartedNotification__McpToolCallStatus; @@ -21464,6 +23515,7 @@ export type V2TurnStartedNotification__ThreadItem = readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; + readonly namespace?: string | null; readonly status: V2TurnStartedNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; @@ -21487,12 +23539,16 @@ export type V2TurnStartedNotification__ThreadItem = readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2TurnStartedNotification__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2TurnStartedNotification__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } @@ -21551,7 +23607,10 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -21613,6 +23672,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( Schema.Union([V2TurnStartedNotification__McpToolCallError, Schema.Null]), ), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__McpToolCallResult, Schema.Null]), ), @@ -21639,6 +23699,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( ]), ), id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: V2TurnStartedNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, @@ -21702,14 +23763,16 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2TurnStartedNotification__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -21799,6 +23862,7 @@ export type V2TurnStartResponse__ThreadItem = readonly durationMs?: number | null; readonly error?: V2TurnStartResponse__McpToolCallError | null; readonly id: string; + readonly mcpAppResourceUri?: string | null; readonly result?: V2TurnStartResponse__McpToolCallResult | null; readonly server: string; readonly status: V2TurnStartResponse__McpToolCallStatus; @@ -21810,6 +23874,7 @@ export type V2TurnStartResponse__ThreadItem = readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; + readonly namespace?: string | null; readonly status: V2TurnStartResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; @@ -21833,12 +23898,16 @@ export type V2TurnStartResponse__ThreadItem = readonly query: string; readonly type: "webSearch"; } - | { readonly id: string; readonly path: string; readonly type: "imageView" } + | { + readonly id: string; + readonly path: V2TurnStartResponse__AbsolutePathBuf; + readonly type: "imageView"; + } | { readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: string | null; + readonly savedPath?: V2TurnStartResponse__AbsolutePathBuf | null; readonly status: string; readonly type: "imageGeneration"; } @@ -21895,7 +23964,10 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( description: "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", }), - cwd: Schema.String.annotate({ description: "The command's working directory." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ @@ -21955,6 +24027,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( ), error: Schema.optionalKey(Schema.Union([V2TurnStartResponse__McpToolCallError, Schema.Null])), id: Schema.String, + mcpAppResourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnStartResponse__McpToolCallResult, Schema.Null]), ), @@ -21981,6 +24054,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( ]), ), id: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), status: V2TurnStartResponse__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, @@ -22041,14 +24115,16 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: Schema.String, + path: V2TurnStartResponse__AbsolutePathBuf, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - savedPath: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__AbsolutePathBuf, Schema.Null]), + ), status: Schema.String, type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), @@ -22076,6 +24152,50 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( { mode: "oneOf" }, ); +export type ClientRequest__ExternalAgentConfigImportParams = { + readonly migrationItems: ReadonlyArray; +}; +export const ClientRequest__ExternalAgentConfigImportParams = Schema.Struct({ + migrationItems: Schema.Array(ClientRequest__ExternalAgentConfigMigrationItem), +}); + +export type CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions = Schema.Struct( + { + entries: Schema.optionalKey( + Schema.Union([ + Schema.Array(CommandExecutionRequestApprovalParams__FileSystemSandboxEntry), + Schema.Null, + ]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(CommandExecutionRequestApprovalParams__AbsolutePathBuf), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(CommandExecutionRequestApprovalParams__AbsolutePathBuf), + Schema.Null, + ]), + ), + }, +); + export type McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema = | McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema | McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema; @@ -22084,6 +24204,64 @@ export const McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSch McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema, ]); +export type PermissionsRequestApprovalParams__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const PermissionsRequestApprovalParams__AdditionalFileSystemPermissions = Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalParams__FileSystemSandboxEntry), + Schema.Null, + ]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([Schema.Array(PermissionsRequestApprovalParams__AbsolutePathBuf), Schema.Null]), + ), + write: Schema.optionalKey( + Schema.Union([Schema.Array(PermissionsRequestApprovalParams__AbsolutePathBuf), Schema.Null]), + ), +}); + +export type PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalResponse__FileSystemSandboxEntry), + Schema.Null, + ]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([Schema.Array(PermissionsRequestApprovalResponse__AbsolutePathBuf), Schema.Null]), + ), + write: Schema.optionalKey( + Schema.Union([Schema.Array(PermissionsRequestApprovalResponse__AbsolutePathBuf), Schema.Null]), + ), +}); + export type ServerNotification__AppListUpdatedNotification = { readonly data: ReadonlyArray; }; @@ -22091,6 +24269,32 @@ export const ServerNotification__AppListUpdatedNotification = Schema.Struct({ data: Schema.Array(ServerNotification__AppInfo), }).annotate({ description: "EXPERIMENTAL - notification emitted when the app list changes." }); +export type ServerNotification__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const ServerNotification__AdditionalFileSystemPermissions = Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([Schema.Array(ServerNotification__FileSystemSandboxEntry), Schema.Null]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([Schema.Array(ServerNotification__AbsolutePathBuf), Schema.Null]), + ), + write: Schema.optionalKey( + Schema.Union([Schema.Array(ServerNotification__AbsolutePathBuf), Schema.Null]), + ), +}); + export type ServerNotification__HookCompletedNotification = { readonly run: ServerNotification__HookRunSummary; readonly threadId: string; @@ -22198,19 +24402,30 @@ export const ServerNotification__Turn = Schema.Struct({ status: ServerNotification__TurnStatus, }); -export type ServerRequest__PermissionsRequestApprovalParams = { - readonly itemId: string; - readonly permissions: ServerRequest__RequestPermissionProfile; - readonly reason?: string | null; - readonly threadId: string; - readonly turnId: string; +export type ServerRequest__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; }; -export const ServerRequest__PermissionsRequestApprovalParams = Schema.Struct({ - itemId: Schema.String, - permissions: ServerRequest__RequestPermissionProfile, - reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - threadId: Schema.String, - turnId: Schema.String, +export const ServerRequest__AdditionalFileSystemPermissions = Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([Schema.Array(ServerRequest__FileSystemSandboxEntry), Schema.Null]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([Schema.Array(ServerRequest__AbsolutePathBuf), Schema.Null]), + ), + write: Schema.optionalKey( + Schema.Union([Schema.Array(ServerRequest__AbsolutePathBuf), Schema.Null]), + ), }); export type ServerRequest__McpElicitationMultiSelectEnumSchema = @@ -22265,25 +24480,97 @@ export const V2ConfigReadResponse__ProfileV2 = Schema.StructWithRest( web_search: Schema.optionalKey( Schema.Union([V2ConfigReadResponse__WebSearchMode, Schema.Null]), ), - }), - [Schema.Record(Schema.String, Schema.Unknown)], -); - -export type V2ConfigWriteResponse__OverriddenMetadata = { - readonly effectiveValue: unknown; - readonly message: string; - readonly overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata; -}; -export const V2ConfigWriteResponse__OverriddenMetadata = Schema.Struct({ - effectiveValue: Schema.Unknown, - message: Schema.String, - overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata, -}); + }), + [Schema.Record(Schema.String, Schema.Unknown)], +); + +export type V2ConfigWriteResponse__OverriddenMetadata = { + readonly effectiveValue: unknown; + readonly message: string; + readonly overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata; +}; +export const V2ConfigWriteResponse__OverriddenMetadata = Schema.Struct({ + effectiveValue: Schema.Unknown, + message: Schema.String, + overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata, +}); + +export type V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions = + Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry), + Schema.Null, + ]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf), + Schema.Null, + ]), + ), + }); + +export type V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +export const V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions = + Schema.Struct({ + entries: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry), + Schema.Null, + ]), + ), + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(1)), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf), + Schema.Null, + ]), + ), + }); export type V2PluginListResponse__PluginMarketplaceEntry = { readonly interface?: V2PluginListResponse__MarketplaceInterface | null; readonly name: string; - readonly path: V2PluginListResponse__AbsolutePathBuf; + readonly path?: V2PluginListResponse__AbsolutePathBuf | null; readonly plugins: ReadonlyArray; }; export const V2PluginListResponse__PluginMarketplaceEntry = Schema.Struct({ @@ -22291,7 +24578,12 @@ export const V2PluginListResponse__PluginMarketplaceEntry = Schema.Struct({ Schema.Union([V2PluginListResponse__MarketplaceInterface, Schema.Null]), ), name: Schema.String, - path: V2PluginListResponse__AbsolutePathBuf, + path: Schema.optionalKey( + Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: + "Local marketplace file path when the marketplace is backed by a local file. Remote-only catalog marketplaces do not have a local path.", + }), + ), plugins: Schema.Array(V2PluginListResponse__PluginSummary), }); @@ -23000,6 +25292,56 @@ export const V2ThreadStartResponse__Turn = Schema.Struct({ status: V2ThreadStartResponse__TurnStatus, }); +export type V2ThreadTurnsListResponse__Turn = { + readonly completedAt?: number | null; + readonly durationMs?: number | null; + readonly error?: V2ThreadTurnsListResponse__TurnError | null; + readonly id: string; + readonly items: ReadonlyArray; + readonly startedAt?: number | null; + readonly status: V2ThreadTurnsListResponse__TurnStatus; +}; +export const V2ThreadTurnsListResponse__Turn = Schema.Struct({ + completedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the turn completed.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Duration between turn start and completion in milliseconds, if known.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + error: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__TurnError, Schema.Null]).annotate({ + description: "Only populated when the Turn's status is failed.", + }), + ), + id: Schema.String, + items: Schema.Array(V2ThreadTurnsListResponse__ThreadItem).annotate({ + description: + "Only populated on a `thread/resume` or `thread/fork` response. For all other responses and notifications returning a Turn, the items field will be an empty list.", + }), + startedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the turn started.", + format: "int64", + }).check(Schema.isInt()), + Schema.Null, + ]), + ), + status: V2ThreadTurnsListResponse__TurnStatus, +}); + export type V2ThreadUnarchiveResponse__Turn = { readonly completedAt?: number | null; readonly durationMs?: number | null; @@ -23210,6 +25552,48 @@ export const McpServerElicitationRequestParams__McpElicitationEnumSchema = Schem McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema, ]); +export type PermissionsRequestApprovalParams__RequestPermissionProfile = { + readonly fileSystem?: PermissionsRequestApprovalParams__AdditionalFileSystemPermissions | null; + readonly network?: PermissionsRequestApprovalParams__AdditionalNetworkPermissions | null; +}; +export const PermissionsRequestApprovalParams__RequestPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__AdditionalFileSystemPermissions, Schema.Null]), + ), + network: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__AdditionalNetworkPermissions, Schema.Null]), + ), +}); + +export type PermissionsRequestApprovalResponse__GrantedPermissionProfile = { + readonly fileSystem?: PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions | null; + readonly network?: PermissionsRequestApprovalResponse__AdditionalNetworkPermissions | null; +}; +export const PermissionsRequestApprovalResponse__GrantedPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ + PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions, + Schema.Null, + ]), + ), + network: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalResponse__AdditionalNetworkPermissions, Schema.Null]), + ), +}); + +export type ServerNotification__RequestPermissionProfile = { + readonly fileSystem?: ServerNotification__AdditionalFileSystemPermissions | null; + readonly network?: ServerNotification__AdditionalNetworkPermissions | null; +}; +export const ServerNotification__RequestPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ServerNotification__AdditionalFileSystemPermissions, Schema.Null]), + ), + network: Schema.optionalKey( + Schema.Union([ServerNotification__AdditionalNetworkPermissions, Schema.Null]), + ), +}); + export type ServerNotification__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; @@ -23268,7 +25652,10 @@ export const ServerNotification__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -23362,6 +25749,19 @@ export const ServerNotification__TurnStartedNotification = Schema.Struct({ turn: ServerNotification__Turn, }); +export type ServerRequest__RequestPermissionProfile = { + readonly fileSystem?: ServerRequest__AdditionalFileSystemPermissions | null; + readonly network?: ServerRequest__AdditionalNetworkPermissions | null; +}; +export const ServerRequest__RequestPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ServerRequest__AdditionalFileSystemPermissions, Schema.Null]), + ), + network: Schema.optionalKey( + Schema.Union([ServerRequest__AdditionalNetworkPermissions, Schema.Null]), + ), +}); + export type ServerRequest__McpElicitationEnumSchema = | ServerRequest__McpElicitationSingleSelectEnumSchema | ServerRequest__McpElicitationMultiSelectEnumSchema @@ -23464,6 +25864,46 @@ export const V2ConfigReadResponse__Config = Schema.StructWithRest( [Schema.Record(Schema.String, Schema.Unknown)], ); +export type V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile = { + readonly fileSystem?: V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions | null; + readonly network?: V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions | null; +}; +export const V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile = + Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions, + Schema.Null, + ]), + ), + network: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions, + Schema.Null, + ]), + ), + }); + +export type V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile = { + readonly fileSystem?: V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions | null; + readonly network?: V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions | null; +}; +export const V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile = + Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions, + Schema.Null, + ]), + ), + network: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions, + Schema.Null, + ]), + ), + }); + export type V2ThreadForkResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; @@ -23522,7 +25962,10 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -23656,7 +26099,10 @@ export const V2ThreadListResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -23790,7 +26236,10 @@ export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -23924,7 +26373,10 @@ export const V2ThreadReadResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -24058,7 +26510,10 @@ export const V2ThreadResumeResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -24192,7 +26647,10 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -24326,7 +26784,10 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -24460,7 +26921,10 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -24548,6 +27012,101 @@ export const McpServerElicitationRequestParams__McpElicitationPrimitiveSchema = McpServerElicitationRequestParams__McpElicitationBooleanSchema, ]); +export type ServerNotification__GuardianApprovalReviewAction = + | { + readonly command: string; + readonly cwd: ServerNotification__AbsolutePathBuf; + readonly source: ServerNotification__GuardianCommandSource; + readonly type: "command"; + } + | { + readonly argv: ReadonlyArray; + readonly cwd: ServerNotification__AbsolutePathBuf; + readonly program: string; + readonly source: ServerNotification__GuardianCommandSource; + readonly type: "execve"; + } + | { + readonly cwd: ServerNotification__AbsolutePathBuf; + readonly files: ReadonlyArray; + readonly type: "applyPatch"; + } + | { + readonly host: string; + readonly port: number; + readonly protocol: ServerNotification__NetworkApprovalProtocol; + readonly target: string; + readonly type: "networkAccess"; + } + | { + readonly connectorId?: string | null; + readonly connectorName?: string | null; + readonly server: string; + readonly toolName: string; + readonly toolTitle?: string | null; + readonly type: "mcpToolCall"; + } + | { + readonly permissions: ServerNotification__RequestPermissionProfile; + readonly reason?: string | null; + readonly type: "requestPermissions"; + }; +export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + cwd: ServerNotification__AbsolutePathBuf, + source: ServerNotification__GuardianCommandSource, + type: Schema.Literal("command").annotate({ + title: "CommandGuardianApprovalReviewActionType", + }), + }).annotate({ title: "CommandGuardianApprovalReviewAction" }), + Schema.Struct({ + argv: Schema.Array(Schema.String), + cwd: ServerNotification__AbsolutePathBuf, + program: Schema.String, + source: ServerNotification__GuardianCommandSource, + type: Schema.Literal("execve").annotate({ title: "ExecveGuardianApprovalReviewActionType" }), + }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), + Schema.Struct({ + cwd: ServerNotification__AbsolutePathBuf, + files: Schema.Array(ServerNotification__AbsolutePathBuf), + type: Schema.Literal("applyPatch").annotate({ + title: "ApplyPatchGuardianApprovalReviewActionType", + }), + }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), + Schema.Struct({ + host: Schema.String, + port: Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + protocol: ServerNotification__NetworkApprovalProtocol, + target: Schema.String, + type: Schema.Literal("networkAccess").annotate({ + title: "NetworkAccessGuardianApprovalReviewActionType", + }), + }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), + Schema.Struct({ + connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + server: Schema.String, + toolName: Schema.String, + toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("mcpToolCall").annotate({ + title: "McpToolCallGuardianApprovalReviewActionType", + }), + }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), + Schema.Struct({ + permissions: ServerNotification__RequestPermissionProfile, + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("requestPermissions").annotate({ + title: "RequestPermissionsGuardianApprovalReviewActionType", + }), + }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), + ], + { mode: "oneOf" }, +); + export type ServerNotification__ThreadStartedNotification = { readonly thread: ServerNotification__Thread; }; @@ -24555,6 +27114,23 @@ export const ServerNotification__ThreadStartedNotification = Schema.Struct({ thread: ServerNotification__Thread, }); +export type ServerRequest__PermissionsRequestApprovalParams = { + readonly cwd: ServerRequest__AbsolutePathBuf; + readonly itemId: string; + readonly permissions: ServerRequest__RequestPermissionProfile; + readonly reason?: string | null; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerRequest__PermissionsRequestApprovalParams = Schema.Struct({ + cwd: ServerRequest__AbsolutePathBuf, + itemId: Schema.String, + permissions: ServerRequest__RequestPermissionProfile, + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + threadId: Schema.String, + turnId: Schema.String, +}); + export type ServerRequest__McpElicitationPrimitiveSchema = | ServerRequest__McpElicitationEnumSchema | ServerRequest__McpElicitationStringSchema @@ -24567,6 +27143,202 @@ export const ServerRequest__McpElicitationPrimitiveSchema = Schema.Union([ ServerRequest__McpElicitationBooleanSchema, ]); +export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction = + | { + readonly command: string; + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; + readonly source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource; + readonly type: "command"; + } + | { + readonly argv: ReadonlyArray; + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; + readonly program: string; + readonly source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource; + readonly type: "execve"; + } + | { + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; + readonly files: ReadonlyArray; + readonly type: "applyPatch"; + } + | { + readonly host: string; + readonly port: number; + readonly protocol: V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol; + readonly target: string; + readonly type: "networkAccess"; + } + | { + readonly connectorId?: string | null; + readonly connectorName?: string | null; + readonly server: string; + readonly toolName: string; + readonly toolTitle?: string | null; + readonly type: "mcpToolCall"; + } + | { + readonly permissions: V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile; + readonly reason?: string | null; + readonly type: "requestPermissions"; + }; +export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction = + Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, + source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource, + type: Schema.Literal("command").annotate({ + title: "CommandGuardianApprovalReviewActionType", + }), + }).annotate({ title: "CommandGuardianApprovalReviewAction" }), + Schema.Struct({ + argv: Schema.Array(Schema.String), + cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, + program: Schema.String, + source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource, + type: Schema.Literal("execve").annotate({ + title: "ExecveGuardianApprovalReviewActionType", + }), + }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), + Schema.Struct({ + cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, + files: Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf), + type: Schema.Literal("applyPatch").annotate({ + title: "ApplyPatchGuardianApprovalReviewActionType", + }), + }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), + Schema.Struct({ + host: Schema.String, + port: Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + protocol: V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol, + target: Schema.String, + type: Schema.Literal("networkAccess").annotate({ + title: "NetworkAccessGuardianApprovalReviewActionType", + }), + }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), + Schema.Struct({ + connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + server: Schema.String, + toolName: Schema.String, + toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("mcpToolCall").annotate({ + title: "McpToolCallGuardianApprovalReviewActionType", + }), + }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), + Schema.Struct({ + permissions: V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile, + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("requestPermissions").annotate({ + title: "RequestPermissionsGuardianApprovalReviewActionType", + }), + }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), + ], + { mode: "oneOf" }, + ); + +export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction = + | { + readonly command: string; + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; + readonly source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource; + readonly type: "command"; + } + | { + readonly argv: ReadonlyArray; + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; + readonly program: string; + readonly source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource; + readonly type: "execve"; + } + | { + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; + readonly files: ReadonlyArray; + readonly type: "applyPatch"; + } + | { + readonly host: string; + readonly port: number; + readonly protocol: V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol; + readonly target: string; + readonly type: "networkAccess"; + } + | { + readonly connectorId?: string | null; + readonly connectorName?: string | null; + readonly server: string; + readonly toolName: string; + readonly toolTitle?: string | null; + readonly type: "mcpToolCall"; + } + | { + readonly permissions: V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile; + readonly reason?: string | null; + readonly type: "requestPermissions"; + }; +export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction = + Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, + source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource, + type: Schema.Literal("command").annotate({ + title: "CommandGuardianApprovalReviewActionType", + }), + }).annotate({ title: "CommandGuardianApprovalReviewAction" }), + Schema.Struct({ + argv: Schema.Array(Schema.String), + cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, + program: Schema.String, + source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource, + type: Schema.Literal("execve").annotate({ + title: "ExecveGuardianApprovalReviewActionType", + }), + }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), + Schema.Struct({ + cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, + files: Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf), + type: Schema.Literal("applyPatch").annotate({ + title: "ApplyPatchGuardianApprovalReviewActionType", + }), + }).annotate({ title: "ApplyPatchGuardianApprovalReviewAction" }), + Schema.Struct({ + host: Schema.String, + port: Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + protocol: V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol, + target: Schema.String, + type: Schema.Literal("networkAccess").annotate({ + title: "NetworkAccessGuardianApprovalReviewActionType", + }), + }).annotate({ title: "NetworkAccessGuardianApprovalReviewAction" }), + Schema.Struct({ + connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + server: Schema.String, + toolName: Schema.String, + toolTitle: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("mcpToolCall").annotate({ + title: "McpToolCallGuardianApprovalReviewActionType", + }), + }).annotate({ title: "McpToolCallGuardianApprovalReviewAction" }), + Schema.Struct({ + permissions: V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile, + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("requestPermissions").annotate({ + title: "RequestPermissionsGuardianApprovalReviewActionType", + }), + }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), + ], + { mode: "oneOf" }, + ); + export type McpServerElicitationRequestParams__McpElicitationSchema = { readonly $schema?: string | null; readonly properties: { @@ -24588,6 +27360,64 @@ export const McpServerElicitationRequestParams__McpElicitationSchema = Schema.St "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.", }); +export type ServerNotification__ItemGuardianApprovalReviewCompletedNotification = { + readonly action: ServerNotification__GuardianApprovalReviewAction; + readonly decisionSource: ServerNotification__AutoReviewDecisionSource; + readonly review: ServerNotification__GuardianApprovalReview; + readonly reviewId: string; + readonly targetItemId?: string | null; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ + action: ServerNotification__GuardianApprovalReviewAction, + decisionSource: ServerNotification__AutoReviewDecisionSource, + review: ServerNotification__GuardianApprovalReview, + reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), + targetItemId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + }), + Schema.Null, + ]), + ), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ + description: + "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", +}); + +export type ServerNotification__ItemGuardianApprovalReviewStartedNotification = { + readonly action: ServerNotification__GuardianApprovalReviewAction; + readonly review: ServerNotification__GuardianApprovalReview; + readonly reviewId: string; + readonly targetItemId?: string | null; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ + action: ServerNotification__GuardianApprovalReviewAction, + review: ServerNotification__GuardianApprovalReview, + reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), + targetItemId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + }), + Schema.Null, + ]), + ), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ + description: + "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", +}); + export type ServerRequest__McpElicitationSchema = { readonly $schema?: string | null; readonly properties: { readonly [x: string]: ServerRequest__McpElicitationPrimitiveSchema }; @@ -24819,11 +27649,31 @@ export type ClientRequest = readonly method: "thread/read"; readonly params: ClientRequest__ThreadReadParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/turns/list"; + readonly params: ClientRequest__ThreadTurnsListParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/inject_items"; + readonly params: ClientRequest__ThreadInjectItemsParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "skills/list"; readonly params: ClientRequest__SkillsListParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "marketplace/add"; + readonly params: ClientRequest__MarketplaceAddParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "marketplace/remove"; + readonly params: ClientRequest__MarketplaceRemoveParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "plugin/list"; @@ -24839,6 +27689,21 @@ export type ClientRequest = readonly method: "app/list"; readonly params: ClientRequest__AppsListParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "device/key/create"; + readonly params: ClientRequest__DeviceKeyCreateParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "device/key/public"; + readonly params: ClientRequest__DeviceKeyPublicParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "device/key/sign"; + readonly params: ClientRequest__DeviceKeySignParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "fs/readFile"; @@ -24984,6 +27849,11 @@ export type ClientRequest = readonly method: "account/rateLimits/read"; readonly params?: null; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "account/sendAddCreditsNudgeEmail"; + readonly params: ClientRequest__SendAddCreditsNudgeEmailParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "feedback/upload"; @@ -25138,11 +28008,41 @@ export const ClientRequest = Schema.Union( method: Schema.Literal("thread/read").annotate({ title: "Thread/readRequestMethod" }), params: ClientRequest__ThreadReadParams, }).annotate({ title: "Thread/readRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/turns/list").annotate({ + title: "Thread/turns/listRequestMethod", + }), + params: ClientRequest__ThreadTurnsListParams, + }).annotate({ title: "Thread/turns/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/inject_items").annotate({ + title: "Thread/injectItemsRequestMethod", + }), + params: ClientRequest__ThreadInjectItemsParams, + }).annotate({ + title: "Thread/injectItemsRequest", + description: + "Append raw Responses API items to the thread history without starting a user turn.", + }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("skills/list").annotate({ title: "Skills/listRequestMethod" }), params: ClientRequest__SkillsListParams, }).annotate({ title: "Skills/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("marketplace/add").annotate({ title: "Marketplace/addRequestMethod" }), + params: ClientRequest__MarketplaceAddParams, + }).annotate({ title: "Marketplace/addRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("marketplace/remove").annotate({ + title: "Marketplace/removeRequestMethod", + }), + params: ClientRequest__MarketplaceRemoveParams, + }).annotate({ title: "Marketplace/removeRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("plugin/list").annotate({ title: "Plugin/listRequestMethod" }), @@ -25158,6 +28058,25 @@ export const ClientRequest = Schema.Union( method: Schema.Literal("app/list").annotate({ title: "App/listRequestMethod" }), params: ClientRequest__AppsListParams, }).annotate({ title: "App/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("device/key/create").annotate({ + title: "Device/key/createRequestMethod", + }), + params: ClientRequest__DeviceKeyCreateParams, + }).annotate({ title: "Device/key/createRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("device/key/public").annotate({ + title: "Device/key/publicRequestMethod", + }), + params: ClientRequest__DeviceKeyPublicParams, + }).annotate({ title: "Device/key/publicRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("device/key/sign").annotate({ title: "Device/key/signRequestMethod" }), + params: ClientRequest__DeviceKeySignParams, + }).annotate({ title: "Device/key/signRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("fs/readFile").annotate({ title: "Fs/readFileRequestMethod" }), @@ -25333,6 +28252,13 @@ export const ClientRequest = Schema.Union( }), params: Schema.optionalKey(Schema.Null), }).annotate({ title: "Account/rateLimits/readRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("account/sendAddCreditsNudgeEmail").annotate({ + title: "Account/sendAddCreditsNudgeEmailRequestMethod", + }), + params: ClientRequest__SendAddCreditsNudgeEmailParams, + }).annotate({ title: "Account/sendAddCreditsNudgeEmailRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("feedback/upload").annotate({ title: "Feedback/uploadRequestMethod" }), @@ -25455,12 +28381,14 @@ export type ClientRequest__DynamicToolSpec = { readonly description: string; readonly inputSchema: unknown; readonly name: string; + readonly namespace?: string | null; }; export const ClientRequest__DynamicToolSpec = Schema.Struct({ deferLoading: Schema.optionalKey(Schema.Boolean), description: Schema.String, inputSchema: Schema.Unknown, name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); export type ClientRequest__NetworkAccess = "restricted" | "enabled"; @@ -25489,6 +28417,9 @@ export const ClientRequest__ReadOnlyAccess = Schema.Union( { mode: "oneOf" }, ); +export type ClientRequest__RealtimeOutputModality = "text" | "audio"; +export const ClientRequest__RealtimeOutputModality = Schema.Literals(["text", "audio"]); + export type ClientRequest__RealtimeVoice = | "alloy" | "arbor" @@ -25745,6 +28676,9 @@ export const ClientRequest__ResponseItem = Schema.Union( { mode: "oneOf" }, ); +export type ClientRequest__ThreadMemoryMode = "enabled" | "disabled"; +export const ClientRequest__ThreadMemoryMode = Schema.Literals(["enabled", "disabled"]); + export type ClientRequest__ThreadRealtimeAudioChunk = { readonly data: string; readonly itemId?: string | null; @@ -25796,7 +28730,7 @@ export type CommandExecutionRequestApprovalParams = { readonly approvalId?: string | null; readonly command?: string | null; readonly commandActions?: ReadonlyArray | null; - readonly cwd?: string | null; + readonly cwd?: CommandExecutionRequestApprovalParams__AbsolutePathBuf | null; readonly itemId: string; readonly networkApprovalContext?: CommandExecutionRequestApprovalParams__NetworkApprovalContext | null; readonly proposedExecpolicyAmendment?: ReadonlyArray | null; @@ -25830,10 +28764,9 @@ export const CommandExecutionRequestApprovalParams = Schema.Struct({ ]), ), cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "The command's working directory." }), - Schema.Null, - ]), + Schema.Union([CommandExecutionRequestApprovalParams__AbsolutePathBuf, Schema.Null]).annotate({ + description: "The command's working directory.", + }), ), itemId: Schema.String, networkApprovalContext: Schema.optionalKey( @@ -25950,6 +28883,7 @@ export const CommandExecutionRequestApprovalResponse = Schema.Struct({ export type DynamicToolCallParams = { readonly arguments: unknown; readonly callId: string; + readonly namespace?: string | null; readonly threadId: string; readonly tool: string; readonly turnId: string; @@ -25957,6 +28891,7 @@ export type DynamicToolCallParams = { export const DynamicToolCallParams = Schema.Struct({ arguments: Schema.Unknown, callId: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), threadId: Schema.String, tool: Schema.String, turnId: Schema.String, @@ -26276,6 +29211,7 @@ export const McpServerElicitationRequestResponse = Schema.Struct({ }).annotate({ title: "McpServerElicitationRequestResponse" }); export type PermissionsRequestApprovalParams = { + readonly cwd: PermissionsRequestApprovalParams__AbsolutePathBuf; readonly itemId: string; readonly permissions: PermissionsRequestApprovalParams__RequestPermissionProfile; readonly reason?: string | null; @@ -26283,6 +29219,7 @@ export type PermissionsRequestApprovalParams = { readonly turnId: string; }; export const PermissionsRequestApprovalParams = Schema.Struct({ + cwd: PermissionsRequestApprovalParams__AbsolutePathBuf, itemId: Schema.String, permissions: PermissionsRequestApprovalParams__RequestPermissionProfile, reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -26409,6 +29346,10 @@ export type ServerNotification = readonly method: "item/fileChange/outputDelta"; readonly params: ServerNotification__FileChangeOutputDeltaNotification; } + | { + readonly method: "item/fileChange/patchUpdated"; + readonly params: ServerNotification__FileChangePatchUpdatedNotification; + } | { readonly method: "serverRequest/resolved"; readonly params: ServerNotification__ServerRequestResolvedNotification; @@ -26437,6 +29378,10 @@ export type ServerNotification = readonly method: "app/list/updated"; readonly params: ServerNotification__AppListUpdatedNotification; } + | { + readonly method: "externalAgentConfig/import/completed"; + readonly params: ServerNotification__ExternalAgentConfigImportCompletedNotification; + } | { readonly method: "fs/changed"; readonly params: ServerNotification__FsChangedNotification } | { readonly method: "item/reasoning/summaryTextDelta"; @@ -26458,6 +29403,7 @@ export type ServerNotification = readonly method: "model/rerouted"; readonly params: ServerNotification__ModelReroutedNotification; } + | { readonly method: "warning"; readonly params: ServerNotification__WarningNotification } | { readonly method: "deprecationNotice"; readonly params: ServerNotification__DeprecationNoticeNotification; @@ -26483,8 +29429,12 @@ export type ServerNotification = readonly params: ServerNotification__ThreadRealtimeItemAddedNotification; } | { - readonly method: "thread/realtime/transcriptUpdated"; - readonly params: ServerNotification__ThreadRealtimeTranscriptUpdatedNotification; + readonly method: "thread/realtime/transcript/delta"; + readonly params: ServerNotification__ThreadRealtimeTranscriptDeltaNotification; + } + | { + readonly method: "thread/realtime/transcript/done"; + readonly params: ServerNotification__ThreadRealtimeTranscriptDoneNotification; } | { readonly method: "thread/realtime/outputAudio/delta"; @@ -26665,6 +29615,12 @@ export const ServerNotification = Schema.Union( }), params: ServerNotification__FileChangeOutputDeltaNotification, }).annotate({ title: "Item/fileChange/outputDeltaNotification" }), + Schema.Struct({ + method: Schema.Literal("item/fileChange/patchUpdated").annotate({ + title: "Item/fileChange/patchUpdatedNotificationMethod", + }), + params: ServerNotification__FileChangePatchUpdatedNotification, + }).annotate({ title: "Item/fileChange/patchUpdatedNotification" }), Schema.Struct({ method: Schema.Literal("serverRequest/resolved").annotate({ title: "ServerRequest/resolvedNotificationMethod", @@ -26707,6 +29663,12 @@ export const ServerNotification = Schema.Union( }), params: ServerNotification__AppListUpdatedNotification, }).annotate({ title: "App/list/updatedNotification" }), + Schema.Struct({ + method: Schema.Literal("externalAgentConfig/import/completed").annotate({ + title: "ExternalAgentConfig/import/completedNotificationMethod", + }), + params: ServerNotification__ExternalAgentConfigImportCompletedNotification, + }).annotate({ title: "ExternalAgentConfig/import/completedNotification" }), Schema.Struct({ method: Schema.Literal("fs/changed").annotate({ title: "Fs/changedNotificationMethod" }), params: ServerNotification__FsChangedNotification, @@ -26744,6 +29706,10 @@ export const ServerNotification = Schema.Union( }), params: ServerNotification__ModelReroutedNotification, }).annotate({ title: "Model/reroutedNotification" }), + Schema.Struct({ + method: Schema.Literal("warning").annotate({ title: "WarningNotificationMethod" }), + params: ServerNotification__WarningNotification, + }).annotate({ title: "WarningNotification" }), Schema.Struct({ method: Schema.Literal("deprecationNotice").annotate({ title: "DeprecationNoticeNotificationMethod", @@ -26781,11 +29747,17 @@ export const ServerNotification = Schema.Union( params: ServerNotification__ThreadRealtimeItemAddedNotification, }).annotate({ title: "Thread/realtime/itemAddedNotification" }), Schema.Struct({ - method: Schema.Literal("thread/realtime/transcriptUpdated").annotate({ - title: "Thread/realtime/transcriptUpdatedNotificationMethod", + method: Schema.Literal("thread/realtime/transcript/delta").annotate({ + title: "Thread/realtime/transcript/deltaNotificationMethod", + }), + params: ServerNotification__ThreadRealtimeTranscriptDeltaNotification, + }).annotate({ title: "Thread/realtime/transcript/deltaNotification" }), + Schema.Struct({ + method: Schema.Literal("thread/realtime/transcript/done").annotate({ + title: "Thread/realtime/transcript/doneNotificationMethod", }), - params: ServerNotification__ThreadRealtimeTranscriptUpdatedNotification, - }).annotate({ title: "Thread/realtime/transcriptUpdatedNotification" }), + params: ServerNotification__ThreadRealtimeTranscriptDoneNotification, + }).annotate({ title: "Thread/realtime/transcript/doneNotification" }), Schema.Struct({ method: Schema.Literal("thread/realtime/outputAudio/delta").annotate({ title: "Thread/realtime/outputAudio/deltaNotificationMethod", @@ -26888,6 +29860,26 @@ export const ServerNotification__CommandExecutionSource = Schema.Literals([ "unifiedExecInteraction", ]); +export type ServerNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const ServerNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]); + export type ServerNotification__SessionSource = | "cli" | "vscode" @@ -27729,7 +30721,6 @@ export type V2ConfigRequirementsReadResponse__NetworkRequirements = { readonly allowUnixSockets?: ReadonlyArray | null; readonly allowUpstreamProxy?: boolean | null; readonly allowedDomains?: ReadonlyArray | null; - readonly dangerFullAccessDenylistOnly?: boolean | null; readonly dangerouslyAllowAllUnixSockets?: boolean | null; readonly dangerouslyAllowNonLoopbackProxy?: boolean | null; readonly deniedDomains?: ReadonlyArray | null; @@ -27763,7 +30754,6 @@ export const V2ConfigRequirementsReadResponse__NetworkRequirements = Schema.Stru Schema.Null, ]), ), - dangerFullAccessDenylistOnly: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), dangerouslyAllowAllUnixSockets: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), dangerouslyAllowNonLoopbackProxy: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), deniedDomains: Schema.optionalKey( @@ -27917,6 +30907,97 @@ export const V2DeprecationNoticeNotification = Schema.Struct({ summary: Schema.String.annotate({ description: "Concise summary of what is deprecated." }), }).annotate({ title: "DeprecationNoticeNotification" }); +export type V2DeviceKeyCreateParams = { + readonly accountUserId: string; + readonly clientId: string; + readonly protectionPolicy?: V2DeviceKeyCreateParams__DeviceKeyProtectionPolicy | null; +}; +export const V2DeviceKeyCreateParams = Schema.Struct({ + accountUserId: Schema.String, + clientId: Schema.String, + protectionPolicy: Schema.optionalKey( + Schema.Union([V2DeviceKeyCreateParams__DeviceKeyProtectionPolicy, Schema.Null]).annotate({ + description: "Defaults to `hardware_only` when omitted.", + }), + ), +}).annotate({ + title: "DeviceKeyCreateParams", + description: "Create a controller-local device key with a random key id.", +}); + +export type V2DeviceKeyCreateResponse = { + readonly algorithm: V2DeviceKeyCreateResponse__DeviceKeyAlgorithm; + readonly keyId: string; + readonly protectionClass: V2DeviceKeyCreateResponse__DeviceKeyProtectionClass; + readonly publicKeySpkiDerBase64: string; +}; +export const V2DeviceKeyCreateResponse = Schema.Struct({ + algorithm: V2DeviceKeyCreateResponse__DeviceKeyAlgorithm, + keyId: Schema.String, + protectionClass: V2DeviceKeyCreateResponse__DeviceKeyProtectionClass, + publicKeySpkiDerBase64: Schema.String.annotate({ + description: "SubjectPublicKeyInfo DER encoded as base64.", + }), +}).annotate({ + title: "DeviceKeyCreateResponse", + description: "Device-key metadata and public key returned by create/public APIs.", +}); + +export type V2DeviceKeyPublicParams = { readonly keyId: string }; +export const V2DeviceKeyPublicParams = Schema.Struct({ keyId: Schema.String }).annotate({ + title: "DeviceKeyPublicParams", + description: "Fetch a controller-local device key public key by id.", +}); + +export type V2DeviceKeyPublicResponse = { + readonly algorithm: V2DeviceKeyPublicResponse__DeviceKeyAlgorithm; + readonly keyId: string; + readonly protectionClass: V2DeviceKeyPublicResponse__DeviceKeyProtectionClass; + readonly publicKeySpkiDerBase64: string; +}; +export const V2DeviceKeyPublicResponse = Schema.Struct({ + algorithm: V2DeviceKeyPublicResponse__DeviceKeyAlgorithm, + keyId: Schema.String, + protectionClass: V2DeviceKeyPublicResponse__DeviceKeyProtectionClass, + publicKeySpkiDerBase64: Schema.String.annotate({ + description: "SubjectPublicKeyInfo DER encoded as base64.", + }), +}).annotate({ + title: "DeviceKeyPublicResponse", + description: "Device-key public metadata returned by `device/key/public`.", +}); + +export type V2DeviceKeySignParams = { + readonly keyId: string; + readonly payload: V2DeviceKeySignParams__DeviceKeySignPayload; +}; +export const V2DeviceKeySignParams = Schema.Struct({ + keyId: Schema.String, + payload: V2DeviceKeySignParams__DeviceKeySignPayload, +}).annotate({ + title: "DeviceKeySignParams", + description: "Sign an accepted structured payload with a controller-local device key.", +}); + +export type V2DeviceKeySignResponse = { + readonly algorithm: V2DeviceKeySignResponse__DeviceKeyAlgorithm; + readonly signatureDerBase64: string; + readonly signedPayloadBase64: string; +}; +export const V2DeviceKeySignResponse = Schema.Struct({ + algorithm: V2DeviceKeySignResponse__DeviceKeyAlgorithm, + signatureDerBase64: Schema.String.annotate({ + description: "ECDSA signature DER encoded as base64.", + }), + signedPayloadBase64: Schema.String.annotate({ + description: + "Exact bytes signed by the device key, encoded as base64. Verifiers must verify this byte string directly and must not reserialize `payload`.", + }), +}).annotate({ + title: "DeviceKeySignResponse", + description: "ASN.1 DER signature returned by `device/key/sign`.", +}); + export type V2ErrorNotification = { readonly error: V2ErrorNotification__TurnError; readonly threadId: string; @@ -28033,6 +31114,11 @@ export const V2ExternalAgentConfigDetectResponse = Schema.Struct({ items: Schema.Array(V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem), }).annotate({ title: "ExternalAgentConfigDetectResponse" }); +export type V2ExternalAgentConfigImportCompletedNotification = {}; +export const V2ExternalAgentConfigImportCompletedNotification = Schema.Struct({}).annotate({ + title: "ExternalAgentConfigImportCompletedNotification", +}); + export type V2ExternalAgentConfigImportParams = { readonly migrationItems: ReadonlyArray; }; @@ -28050,6 +31136,7 @@ export type V2FeedbackUploadParams = { readonly extraLogFiles?: ReadonlyArray | null; readonly includeLogs: boolean; readonly reason?: string | null; + readonly tags?: { readonly [x: string]: string } | null; readonly threadId?: string | null; }; export const V2FeedbackUploadParams = Schema.Struct({ @@ -28057,6 +31144,9 @@ export const V2FeedbackUploadParams = Schema.Struct({ extraLogFiles: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), includeLogs: Schema.Boolean, reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + tags: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "FeedbackUploadParams" }); @@ -28078,6 +31168,19 @@ export const V2FileChangeOutputDeltaNotification = Schema.Struct({ turnId: Schema.String, }).annotate({ title: "FileChangeOutputDeltaNotification" }); +export type V2FileChangePatchUpdatedNotification = { + readonly changes: ReadonlyArray; + readonly itemId: string; + readonly threadId: string; + readonly turnId: string; +}; +export const V2FileChangePatchUpdatedNotification = Schema.Struct({ + changes: Schema.Array(V2FileChangePatchUpdatedNotification__FileUpdateChange), + itemId: Schema.String, + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ title: "FileChangePatchUpdatedNotification" }); + export type V2FsChangedNotification = { readonly changedPaths: ReadonlyArray; readonly watchId: string; @@ -28185,6 +31288,7 @@ export type V2FsGetMetadataResponse = { readonly createdAtMs: number; readonly isDirectory: boolean; readonly isFile: boolean; + readonly isSymlink: boolean; readonly modifiedAtMs: number; }; export const V2FsGetMetadataResponse = Schema.Struct({ @@ -28193,10 +31297,11 @@ export const V2FsGetMetadataResponse = Schema.Struct({ format: "int64", }).check(Schema.isInt()), isDirectory: Schema.Boolean.annotate({ - description: "Whether the path currently resolves to a directory.", + description: "Whether the path resolves to a directory.", }), - isFile: Schema.Boolean.annotate({ - description: "Whether the path currently resolves to a regular file.", + isFile: Schema.Boolean.annotate({ description: "Whether the path resolves to a regular file." }), + isSymlink: Schema.Boolean.annotate({ + description: "Whether the path itself is a symbolic link.", }), modifiedAtMs: Schema.Number.annotate({ description: "File modification time in Unix milliseconds when available, otherwise `0`.", @@ -28390,6 +31495,7 @@ export type V2GetAccountRateLimitsResponse = { readonly limitName?: string | null; readonly planType?: V2GetAccountRateLimitsResponse__PlanType | null; readonly primary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; + readonly rateLimitReachedType?: V2GetAccountRateLimitsResponse__RateLimitReachedType | null; readonly secondary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; }; readonly rateLimitsByLimitId?: { @@ -28409,6 +31515,9 @@ export const V2GetAccountRateLimitsResponse = Schema.Struct({ primary: Schema.optionalKey( Schema.Union([V2GetAccountRateLimitsResponse__RateLimitWindow, Schema.Null]), ), + rateLimitReachedType: Schema.optionalKey( + Schema.Union([V2GetAccountRateLimitsResponse__RateLimitReachedType, Schema.Null]), + ), secondary: Schema.optionalKey( Schema.Union([V2GetAccountRateLimitsResponse__RateLimitWindow, Schema.Null]), ), @@ -28445,6 +31554,26 @@ export const V2HookCompletedNotification = Schema.Struct({ turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "HookCompletedNotification" }); +export type V2HookCompletedNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const V2HookCompletedNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]); + export type V2HookStartedNotification = { readonly run: V2HookStartedNotification__HookRunSummary; readonly threadId: string; @@ -28456,6 +31585,26 @@ export const V2HookStartedNotification = Schema.Struct({ turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "HookStartedNotification" }); +export type V2HookStartedNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const V2HookStartedNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]); + export type V2ItemCompletedNotification = { readonly item: V2ItemCompletedNotification__ThreadItem; readonly threadId: string; @@ -28544,7 +31693,7 @@ export const V2ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ }).annotate({ title: "ItemGuardianApprovalReviewCompletedNotification", description: - "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.", + "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", }); export type V2ItemGuardianApprovalReviewStartedNotification = { @@ -28573,7 +31722,7 @@ export const V2ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ }).annotate({ title: "ItemGuardianApprovalReviewStartedNotification", description: - "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.", + "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", }); export type V2ItemStartedNotification = { @@ -28785,14 +31934,52 @@ export const V2LogoutAccountResponse = Schema.Struct({}).annotate({ title: "LogoutAccountResponse", }); +export type V2MarketplaceAddParams = { + readonly refName?: string | null; + readonly source: string; + readonly sparsePaths?: ReadonlyArray | null; +}; +export const V2MarketplaceAddParams = Schema.Struct({ + refName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: Schema.String, + sparsePaths: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), +}).annotate({ title: "MarketplaceAddParams" }); + +export type V2MarketplaceAddResponse = { + readonly alreadyAdded: boolean; + readonly installedRoot: V2MarketplaceAddResponse__AbsolutePathBuf; + readonly marketplaceName: string; +}; +export const V2MarketplaceAddResponse = Schema.Struct({ + alreadyAdded: Schema.Boolean, + installedRoot: V2MarketplaceAddResponse__AbsolutePathBuf, + marketplaceName: Schema.String, +}).annotate({ title: "MarketplaceAddResponse" }); + +export type V2MarketplaceRemoveParams = { readonly marketplaceName: string }; +export const V2MarketplaceRemoveParams = Schema.Struct({ marketplaceName: Schema.String }).annotate( + { title: "MarketplaceRemoveParams" }, +); + +export type V2MarketplaceRemoveResponse = { + readonly installedRoot?: V2MarketplaceRemoveResponse__AbsolutePathBuf | null; + readonly marketplaceName: string; +}; +export const V2MarketplaceRemoveResponse = Schema.Struct({ + installedRoot: Schema.optionalKey( + Schema.Union([V2MarketplaceRemoveResponse__AbsolutePathBuf, Schema.Null]), + ), + marketplaceName: Schema.String, +}).annotate({ title: "MarketplaceRemoveResponse" }); + export type V2McpResourceReadParams = { readonly server: string; - readonly threadId: string; + readonly threadId?: string | null; readonly uri: string; }; export const V2McpResourceReadParams = Schema.Struct({ server: Schema.String, - threadId: Schema.String, + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), uri: Schema.String, }).annotate({ title: "McpResourceReadParams" }); @@ -28974,18 +32161,16 @@ export const V2PlanDeltaNotification = Schema.Struct({ }); export type V2PluginInstallParams = { - readonly forceRemoteSync?: boolean; - readonly marketplacePath: V2PluginInstallParams__AbsolutePathBuf; + readonly marketplacePath?: V2PluginInstallParams__AbsolutePathBuf | null; readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; export const V2PluginInstallParams = Schema.Struct({ - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, apply the remote plugin change before the local install flow.", - }), + marketplacePath: Schema.optionalKey( + Schema.Union([V2PluginInstallParams__AbsolutePathBuf, Schema.Null]), ), - marketplacePath: V2PluginInstallParams__AbsolutePathBuf, pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "PluginInstallParams" }); export type V2PluginInstallResponse = { @@ -28999,7 +32184,6 @@ export const V2PluginInstallResponse = Schema.Struct({ export type V2PluginListParams = { readonly cwds?: ReadonlyArray | null; - readonly forceRemoteSync?: boolean; }; export const V2PluginListParams = Schema.Struct({ cwds: Schema.optionalKey( @@ -29011,19 +32195,12 @@ export const V2PluginListParams = Schema.Struct({ Schema.Null, ]), ), - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "When true, reconcile the official curated marketplace against the remote plugin state before listing marketplaces.", - }), - ), }).annotate({ title: "PluginListParams" }); export type V2PluginListResponse = { readonly featuredPluginIds?: ReadonlyArray; readonly marketplaceLoadErrors?: ReadonlyArray; readonly marketplaces: ReadonlyArray; - readonly remoteSyncError?: string | null; }; export const V2PluginListResponse = Schema.Struct({ featuredPluginIds: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), @@ -29031,16 +32208,19 @@ export const V2PluginListResponse = Schema.Struct({ Schema.Array(V2PluginListResponse__MarketplaceLoadErrorInfo).annotate({ default: [] }), ), marketplaces: Schema.Array(V2PluginListResponse__PluginMarketplaceEntry), - remoteSyncError: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "PluginListResponse" }); export type V2PluginReadParams = { - readonly marketplacePath: V2PluginReadParams__AbsolutePathBuf; + readonly marketplacePath?: V2PluginReadParams__AbsolutePathBuf | null; readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; export const V2PluginReadParams = Schema.Struct({ - marketplacePath: V2PluginReadParams__AbsolutePathBuf, + marketplacePath: Schema.optionalKey( + Schema.Union([V2PluginReadParams__AbsolutePathBuf, Schema.Null]), + ), pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "PluginReadParams" }); export type V2PluginReadResponse = { readonly plugin: V2PluginReadResponse__PluginDetail }; @@ -29048,18 +32228,10 @@ export const V2PluginReadResponse = Schema.Struct({ plugin: V2PluginReadResponse__PluginDetail, }).annotate({ title: "PluginReadResponse" }); -export type V2PluginUninstallParams = { - readonly forceRemoteSync?: boolean; - readonly pluginId: string; -}; -export const V2PluginUninstallParams = Schema.Struct({ - forceRemoteSync: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, apply the remote plugin change before the local uninstall flow.", - }), - ), - pluginId: Schema.String, -}).annotate({ title: "PluginUninstallParams" }); +export type V2PluginUninstallParams = { readonly pluginId: string }; +export const V2PluginUninstallParams = Schema.Struct({ pluginId: Schema.String }).annotate({ + title: "PluginUninstallParams", +}); export type V2PluginUninstallResponse = {}; export const V2PluginUninstallResponse = Schema.Struct({}).annotate({ @@ -29194,6 +32366,20 @@ export const V2ReviewStartResponse__CommandExecutionSource = Schema.Literals([ "unifiedExecInteraction", ]); +export type V2SendAddCreditsNudgeEmailParams = { + readonly creditType: V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType; +}; +export const V2SendAddCreditsNudgeEmailParams = Schema.Struct({ + creditType: V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType, +}).annotate({ title: "SendAddCreditsNudgeEmailParams" }); + +export type V2SendAddCreditsNudgeEmailResponse = { + readonly status: V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus; +}; +export const V2SendAddCreditsNudgeEmailResponse = Schema.Struct({ + status: V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus, +}).annotate({ title: "SendAddCreditsNudgeEmailResponse" }); + export type V2ServerRequestResolvedNotification = { readonly requestId: V2ServerRequestResolvedNotification__RequestId; readonly threadId: string; @@ -29364,7 +32550,8 @@ export const V2ThreadForkParams = Schema.Struct({ export type V2ThreadForkResponse = { readonly approvalPolicy: V2ThreadForkResponse__AskForApproval; readonly approvalsReviewer: "user" | "guardian_subagent"; - readonly cwd: string; + readonly cwd: V2ThreadForkResponse__AbsolutePathBuf; + readonly instructionSources?: ReadonlyArray; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; @@ -29378,7 +32565,13 @@ export const V2ThreadForkResponse = Schema.Struct({ description: "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", }), - cwd: Schema.String, + cwd: V2ThreadForkResponse__AbsolutePathBuf, + instructionSources: Schema.optionalKey( + Schema.Array(V2ThreadForkResponse__AbsolutePathBuf).annotate({ + description: "Instruction source files currently loaded for this thread.", + default: [], + }), + ), model: Schema.String, modelProvider: Schema.String, reasoningEffort: Schema.optionalKey( @@ -29513,6 +32706,22 @@ export const V2ThreadForkResponse__ThreadStatus = Schema.Union( { mode: "oneOf" }, ); +export type V2ThreadInjectItemsParams = { + readonly items: ReadonlyArray; + readonly threadId: string; +}; +export const V2ThreadInjectItemsParams = Schema.Struct({ + items: Schema.Array(Schema.Unknown).annotate({ + description: "Raw Responses API items to append to the thread's model-visible history.", + }), + threadId: Schema.String, +}).annotate({ title: "ThreadInjectItemsParams" }); + +export type V2ThreadInjectItemsResponse = {}; +export const V2ThreadInjectItemsResponse = Schema.Struct({}).annotate({ + title: "ThreadInjectItemsResponse", +}); + export type V2ThreadListParams = { readonly archived?: boolean | null; readonly cursor?: string | null; @@ -29520,6 +32729,7 @@ export type V2ThreadListParams = { readonly limit?: number | null; readonly modelProviders?: ReadonlyArray | null; readonly searchTerm?: string | null; + readonly sortDirection?: V2ThreadListParams__SortDirection | null; readonly sortKey?: V2ThreadListParams__ThreadSortKey | null; readonly sourceKinds?: ReadonlyArray | null; }; @@ -29578,6 +32788,11 @@ export const V2ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + sortDirection: Schema.optionalKey( + Schema.Union([V2ThreadListParams__SortDirection, Schema.Null]).annotate({ + description: "Optional sort direction; defaults to descending (newest first).", + }), + ), sortKey: Schema.optionalKey( Schema.Union([V2ThreadListParams__ThreadSortKey, Schema.Null]).annotate({ description: "Optional sort key; defaults to created_at.", @@ -29595,10 +32810,20 @@ export const V2ThreadListParams = Schema.Struct({ }).annotate({ title: "ThreadListParams" }); export type V2ThreadListResponse = { + readonly backwardsCursor?: string | null; readonly data: ReadonlyArray; readonly nextCursor?: string | null; }; export const V2ThreadListResponse = Schema.Struct({ + backwardsCursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one thread. Use it with the opposite `sortDirection`; for timestamp sorts it anchors at the start of the page timestamp so same-second updates are not skipped.", + }), + Schema.Null, + ]), + ), data: Schema.Array(V2ThreadListResponse__Thread), nextCursor: Schema.optionalKey( Schema.Union([ @@ -30049,21 +33274,36 @@ export const V2ThreadRealtimeStartedNotification = Schema.Struct({ description: "EXPERIMENTAL - emitted when thread realtime startup is accepted.", }); -export type V2ThreadRealtimeTranscriptUpdatedNotification = { +export type V2ThreadRealtimeTranscriptDeltaNotification = { + readonly delta: string; readonly role: string; - readonly text: string; readonly threadId: string; }; -export const V2ThreadRealtimeTranscriptUpdatedNotification = Schema.Struct({ +export const V2ThreadRealtimeTranscriptDeltaNotification = Schema.Struct({ + delta: Schema.String.annotate({ description: "Live transcript delta from the realtime event." }), role: Schema.String, - text: Schema.String, threadId: Schema.String, }).annotate({ - title: "ThreadRealtimeTranscriptUpdatedNotification", + title: "ThreadRealtimeTranscriptDeltaNotification", description: "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.", }); +export type V2ThreadRealtimeTranscriptDoneNotification = { + readonly role: string; + readonly text: string; + readonly threadId: string; +}; +export const V2ThreadRealtimeTranscriptDoneNotification = Schema.Struct({ + role: Schema.String, + text: Schema.String.annotate({ description: "Final complete text for the transcript part." }), + threadId: Schema.String, +}).annotate({ + title: "ThreadRealtimeTranscriptDoneNotification", + description: + "EXPERIMENTAL - final transcript text emitted when realtime completes a transcript part.", +}); + export type V2ThreadResumeParams = { readonly approvalPolicy?: V2ThreadResumeParams__AskForApproval | null; readonly approvalsReviewer?: V2ThreadResumeParams__ApprovalsReviewer | null; @@ -30332,7 +33572,8 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( export type V2ThreadResumeResponse = { readonly approvalPolicy: V2ThreadResumeResponse__AskForApproval; readonly approvalsReviewer: "user" | "guardian_subagent"; - readonly cwd: string; + readonly cwd: V2ThreadResumeResponse__AbsolutePathBuf; + readonly instructionSources?: ReadonlyArray; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; @@ -30346,7 +33587,13 @@ export const V2ThreadResumeResponse = Schema.Struct({ description: "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", }), - cwd: Schema.String, + cwd: V2ThreadResumeResponse__AbsolutePathBuf, + instructionSources: Schema.optionalKey( + Schema.Array(V2ThreadResumeResponse__AbsolutePathBuf).annotate({ + description: "Instruction source files currently loaded for this thread.", + default: [], + }), + ), model: Schema.String, modelProvider: Schema.String, reasoningEffort: Schema.optionalKey( @@ -30557,7 +33804,10 @@ export const V2ThreadRollbackResponse = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -30760,7 +34010,10 @@ export const V2ThreadRollbackResponse__Thread = Schema.Struct({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", }).check(Schema.isInt()), - cwd: Schema.String.annotate({ description: "Working directory captured for the thread." }), + cwd: Schema.String.annotate({ + description: + "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31039,18 +34292,21 @@ export type V2ThreadStartParams__DynamicToolSpec = { readonly description: string; readonly inputSchema: unknown; readonly name: string; + readonly namespace?: string | null; }; export const V2ThreadStartParams__DynamicToolSpec = Schema.Struct({ deferLoading: Schema.optionalKey(Schema.Boolean), description: Schema.String, inputSchema: Schema.Unknown, name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }); export type V2ThreadStartResponse = { readonly approvalPolicy: V2ThreadStartResponse__AskForApproval; readonly approvalsReviewer: "user" | "guardian_subagent"; - readonly cwd: string; + readonly cwd: V2ThreadStartResponse__AbsolutePathBuf; + readonly instructionSources?: ReadonlyArray; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadStartResponse__ReasoningEffort | null; @@ -31064,7 +34320,13 @@ export const V2ThreadStartResponse = Schema.Struct({ description: "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.", }), - cwd: Schema.String, + cwd: V2ThreadStartResponse__AbsolutePathBuf, + instructionSources: Schema.optionalKey( + Schema.Array(V2ThreadStartResponse__AbsolutePathBuf).annotate({ + description: "Instruction source files currently loaded for this thread.", + default: [], + }), + ), model: Schema.String, modelProvider: Schema.String, reasoningEffort: Schema.optionalKey( @@ -31222,6 +34484,110 @@ export const V2ThreadTokenUsageUpdatedNotification = Schema.Struct({ turnId: Schema.String, }).annotate({ title: "ThreadTokenUsageUpdatedNotification" }); +export type V2ThreadTurnsListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly sortDirection?: V2ThreadTurnsListParams__SortDirection | null; + readonly threadId: string; +}; +export const V2ThreadTurnsListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque cursor to pass to the next call to continue after the last turn.", + }), + Schema.Null, + ]), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Null, + ]), + ), + sortDirection: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListParams__SortDirection, Schema.Null]).annotate({ + description: "Optional turn pagination direction; defaults to descending.", + }), + ), + threadId: Schema.String, +}).annotate({ title: "ThreadTurnsListParams" }); + +export type V2ThreadTurnsListResponse = { + readonly backwardsCursor?: string | null; + readonly data: ReadonlyArray; + readonly nextCursor?: string | null; +}; +export const V2ThreadTurnsListResponse = Schema.Struct({ + backwardsCursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one turn. Use it with the opposite `sortDirection` to include the anchor turn again and catch updates to that turn.", + }), + Schema.Null, + ]), + ), + data: Schema.Array(V2ThreadTurnsListResponse__Turn), + nextCursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque cursor to pass to the next call to continue after the last turn. if None, there are no more turns to return.", + }), + Schema.Null, + ]), + ), +}).annotate({ title: "ThreadTurnsListResponse" }); + +export type V2ThreadTurnsListResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadTurnsListResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt()) + .check(Schema.isGreaterThanOrEqualTo(0)), +}); + +export type V2ThreadTurnsListResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent"; +export const V2ThreadTurnsListResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", +]); + +export type V2ThreadTurnsListResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadTurnsListResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]); + +export type V2ThreadTurnsListResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadTurnsListResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]); + export type V2ThreadUnarchivedNotification = { readonly threadId: string }; export const V2ThreadUnarchivedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ title: "ThreadUnarchivedNotification", @@ -31693,6 +35059,19 @@ export const V2TurnSteerResponse = Schema.Struct({ turnId: Schema.String }).anno title: "TurnSteerResponse", }); +export type V2WarningNotification = { readonly message: string; readonly threadId?: string | null }; +export const V2WarningNotification = Schema.Struct({ + message: Schema.String.annotate({ description: "Concise warning message for the user." }), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional thread target when the warning applies to a specific thread.", + }), + Schema.Null, + ]), + ), +}).annotate({ title: "WarningNotification" }); + export type V2WindowsSandboxSetupCompletedNotification = { readonly error?: string | null; readonly mode: V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode; From b8305afa29309e52045987caab91db9b7e481ac0 Mon Sep 17 00:00:00 2001 From: Heinz-G Date: Wed, 22 Apr 2026 03:04:50 +0200 Subject: [PATCH 06/20] fix: increase Claude auth probe timeout to 10s (#2272) Co-authored-by: Heinz Gericke <115458264+heinzgericke@users.noreply.github.com> --- apps/server/src/provider/Layers/ClaudeProvider.ts | 3 ++- apps/server/src/provider/providerSnapshot.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 7c8a4c27a6e..60836210b5d 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -19,6 +19,7 @@ import { import { buildServerProvider, + AUTH_PROBE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, detailFromResult, extractAuthBoolean, @@ -674,7 +675,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( // ── Auth check + subscription detection ──────────────────────────── const authProbe = yield* runClaudeCommand(["auth", "status"]).pipe( - Effect.timeoutOption(DEFAULT_TIMEOUT_MS), + Effect.timeoutOption(AUTH_PROBE_TIMEOUT_MS), Effect.result, ); diff --git a/apps/server/src/provider/providerSnapshot.ts b/apps/server/src/provider/providerSnapshot.ts index 068b7c11578..fcea249e6ef 100644 --- a/apps/server/src/provider/providerSnapshot.ts +++ b/apps/server/src/provider/providerSnapshot.ts @@ -13,6 +13,8 @@ import { normalizeModelSlug } from "@t3tools/shared/model"; import { isWindowsCommandNotFound } from "../processRunner.ts"; export const DEFAULT_TIMEOUT_MS = 4_000; +// Auth status checks involve disk/network lookups and can be slow on first run (especially Windows) +export const AUTH_PROBE_TIMEOUT_MS = 10_000; export interface CommandResult { readonly stdout: string; From e25db3a572a6718a1f3c5a64529395cc88cd5249 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 22 Apr 2026 21:33:16 -0700 Subject: [PATCH 07/20] Fix provider cache atomic write temp path collisions (#2291) Co-authored-by: Cursor Agent Co-authored-by: Julius Marminge --- apps/server/src/atomicWrite.ts | 25 +++++++++++++++++ apps/server/src/keybindings.ts | 16 +++++++---- .../src/provider/providerStatusCache.ts | 28 ++++++------------- apps/server/src/serverRuntimeState.ts | 15 ++++------ apps/server/src/serverSettings.ts | 13 +++++---- 5 files changed, 55 insertions(+), 42 deletions(-) create mode 100644 apps/server/src/atomicWrite.ts diff --git a/apps/server/src/atomicWrite.ts b/apps/server/src/atomicWrite.ts new file mode 100644 index 00000000000..431b2f4a01c --- /dev/null +++ b/apps/server/src/atomicWrite.ts @@ -0,0 +1,25 @@ +import { Effect, FileSystem, Path } from "effect"; +import * as Random from "effect/Random"; + +export const writeFileStringAtomically = (input: { + readonly filePath: string; + readonly contents: string; +}) => + Effect.scoped( + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempFileId = yield* Random.nextUUIDv4; + const targetDirectory = path.dirname(input.filePath); + + yield* fs.makeDirectory(targetDirectory, { recursive: true }); + const tempDirectory = yield* fs.makeTempDirectoryScoped({ + directory: targetDirectory, + prefix: `${path.basename(input.filePath)}.`, + }); + const tempPath = path.join(tempDirectory, `${tempFileId}.tmp`); + + yield* fs.writeFileString(tempPath, input.contents); + yield* fs.rename(tempPath, input.filePath); + }), + ); diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index 9689254c173..165b2edeb0c 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -46,6 +46,7 @@ import { } from "effect"; import * as Semaphore from "effect/Semaphore"; import { ServerConfig } from "./config.ts"; +import { writeFileStringAtomically } from "./atomicWrite.ts"; import { fromLenientJson } from "@t3tools/shared/schemaJson"; type WhenToken = @@ -670,14 +671,17 @@ const makeKeybindings = Effect.gen(function* () { }); const writeConfigAtomically = (rules: readonly KeybindingRule[]) => { - const tempPath = `${keybindingsConfigPath}.${process.pid}.${Date.now()}.tmp`; - return Schema.encodeEffect(KeybindingsConfigPrettyJson)(rules).pipe( Effect.map((encoded) => `${encoded}\n`), - Effect.tap(() => fs.makeDirectory(path.dirname(keybindingsConfigPath), { recursive: true })), - Effect.tap((encoded) => fs.writeFileString(tempPath, encoded)), - Effect.flatMap(() => fs.rename(tempPath, keybindingsConfigPath)), - Effect.ensuring(fs.remove(tempPath, { force: true }).pipe(Effect.ignore({ log: true }))), + Effect.flatMap((encoded) => + writeFileStringAtomically({ + filePath: keybindingsConfigPath, + contents: encoded, + }).pipe( + Effect.provideService(FileSystem.FileSystem, fs), + Effect.provideService(Path.Path, path), + ), + ), Effect.mapError( (cause) => new KeybindingsConfigError({ diff --git a/apps/server/src/provider/providerStatusCache.ts b/apps/server/src/provider/providerStatusCache.ts index 369fca6218c..fdb31eecbed 100644 --- a/apps/server/src/provider/providerStatusCache.ts +++ b/apps/server/src/provider/providerStatusCache.ts @@ -1,6 +1,8 @@ import * as nodePath from "node:path"; import { type ServerProvider, ServerProvider as ServerProviderSchema } from "@t3tools/contracts"; -import { Cause, Effect, FileSystem, Path, Schema } from "effect"; +import { Cause, Effect, FileSystem, Schema } from "effect"; + +import { writeFileStringAtomically } from "../atomicWrite.ts"; export const PROVIDER_CACHE_IDS = [ "codex", @@ -96,22 +98,8 @@ export const readProviderStatusCache = (filePath: string) => export const writeProviderStatusCache = (input: { readonly filePath: string; readonly provider: ServerProvider; -}) => { - const tempPath = `${input.filePath}.${process.pid}.${Date.now()}.tmp`; - return Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const encoded = `${JSON.stringify(input.provider, null, 2)}\n`; - - yield* fs.makeDirectory(path.dirname(input.filePath), { recursive: true }); - yield* fs.writeFileString(tempPath, encoded); - yield* fs.rename(tempPath, input.filePath); - }).pipe( - Effect.ensuring( - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - yield* fs.remove(tempPath, { force: true }).pipe(Effect.ignore({ log: true })); - }), - ), - ); -}; +}) => + writeFileStringAtomically({ + filePath: input.filePath, + contents: `${JSON.stringify(input.provider, null, 2)}\n`, + }); diff --git a/apps/server/src/serverRuntimeState.ts b/apps/server/src/serverRuntimeState.ts index 569e4ac1179..4b300f29c2c 100644 --- a/apps/server/src/serverRuntimeState.ts +++ b/apps/server/src/serverRuntimeState.ts @@ -1,5 +1,6 @@ -import { Effect, FileSystem, Option, Path, Schema } from "effect"; +import { Effect, FileSystem, Option, Schema } from "effect"; +import { writeFileStringAtomically } from "./atomicWrite.ts"; import { type ServerConfigShape } from "./config.ts"; import { formatHostForUrl, isWildcardHost } from "./startupAccess.ts"; @@ -42,15 +43,9 @@ export const persistServerRuntimeState = (input: { readonly path: string; readonly state: PersistedServerRuntimeState; }) => - Effect.gen(function* () { - const fs = yield* FileSystem.FileSystem; - const pathService = yield* Path.Path; - const tempPath = `${input.path}.${process.pid}.${Date.now()}.tmp`; - return yield* fs.makeDirectory(pathService.dirname(input.path), { recursive: true }).pipe( - Effect.flatMap(() => fs.writeFileString(tempPath, `${JSON.stringify(input.state)}\n`)), - Effect.flatMap(() => fs.rename(tempPath, input.path)), - Effect.ensuring(fs.remove(tempPath, { force: true }).pipe(Effect.ignore({ log: true }))), - ); + writeFileStringAtomically({ + filePath: input.path, + contents: `${JSON.stringify(input.state)}\n`, }); export const clearPersistedServerRuntimeState = (path: string) => diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index c47c442a86f..c2147a0f45b 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -39,6 +39,7 @@ import { Cause, } from "effect"; import * as Semaphore from "effect/Semaphore"; +import { writeFileStringAtomically } from "./atomicWrite.ts"; import { ServerConfig } from "./config.ts"; import { type DeepPartial, deepMerge } from "@t3tools/shared/Struct"; import { fromLenientJson } from "@t3tools/shared/schemaJson"; @@ -233,14 +234,14 @@ const makeServerSettings = Effect.gen(function* () { const getSettingsFromCache = Cache.get(settingsCache, cacheKey); const writeSettingsAtomically = (settings: ServerSettings) => { - const tempPath = `${settingsPath}.${process.pid}.${Date.now()}.tmp`; const sparseSettings = stripDefaultServerSettings(settings, DEFAULT_SERVER_SETTINGS) ?? {}; - return Effect.succeed(`${JSON.stringify(sparseSettings, null, 2)}\n`).pipe( - Effect.tap(() => fs.makeDirectory(pathService.dirname(settingsPath), { recursive: true })), - Effect.tap((encoded) => fs.writeFileString(tempPath, encoded)), - Effect.flatMap(() => fs.rename(tempPath, settingsPath)), - Effect.ensuring(fs.remove(tempPath, { force: true }).pipe(Effect.ignore({ log: true }))), + return writeFileStringAtomically({ + filePath: settingsPath, + contents: `${JSON.stringify(sparseSettings, null, 2)}\n`, + }).pipe( + Effect.provideService(FileSystem.FileSystem, fs), + Effect.provideService(Path.Path, pathService), Effect.mapError( (cause) => new ServerSettingsError({ From aa2d385a626c5c659e64f7e1a5bf6b568c3b840f Mon Sep 17 00:00:00 2001 From: Hauke Schnau Date: Thu, 23 Apr 2026 06:37:58 +0200 Subject: [PATCH 08/20] fix(server): restore CODEX_HOME tilde expansion for Codex launches (#2255) --- apps/server/src/provider/Layers/CodexProvider.ts | 3 ++- apps/server/src/provider/Layers/CodexSessionRuntime.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index 13069952ae6..1e48847c52c 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -28,6 +28,7 @@ import { ServerSettingsError } from "@t3tools/contracts"; import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; import { buildServerProvider } from "../providerSnapshot.ts"; import { CodexProvider } from "../Services/CodexProvider.ts"; +import { expandHomePath } from "../../pathExpansion.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import packageJson from "../../../package.json" with { type: "json" }; @@ -222,7 +223,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun command: input.binaryPath, args: ["app-server"], cwd: input.cwd, - ...(input.homePath ? { env: { CODEX_HOME: input.homePath } } : {}), + ...(input.homePath ? { env: { CODEX_HOME: expandHomePath(input.homePath) } } : {}), }), ); const client = yield* Effect.service(CodexClient.CodexAppServerClient).pipe( diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.ts index fbf4aa3dee4..e45e1825ee2 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.ts @@ -30,6 +30,7 @@ import { CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS, CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS, } from "../CodexDeveloperInstructions.ts"; +import { expandHomePath } from "../../pathExpansion.ts"; const PROVIDER = "codex" as const; @@ -683,7 +684,9 @@ export const makeCodexSessionRuntime = ( .spawn( ChildProcess.make(options.binaryPath, ["app-server"], { cwd: options.cwd, - ...(options.homePath ? { env: { ...process.env, CODEX_HOME: options.homePath } } : {}), + ...(options.homePath + ? { env: { ...process.env, CODEX_HOME: expandHomePath(options.homePath) } } + : {}), shell: process.platform === "win32", }), ) From fd3b96b487a9aac8a011c27f977067d63d942ac1 Mon Sep 17 00:00:00 2001 From: Bas Milius Date: Thu, 23 Apr 2026 06:40:27 +0200 Subject: [PATCH 09/20] Add IntelliJ project icon to the list of possible favicon paths (#1651) --- apps/server/src/project/Layers/ProjectFaviconResolver.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/project/Layers/ProjectFaviconResolver.ts b/apps/server/src/project/Layers/ProjectFaviconResolver.ts index 3004a7a45cf..f2d44a76096 100644 --- a/apps/server/src/project/Layers/ProjectFaviconResolver.ts +++ b/apps/server/src/project/Layers/ProjectFaviconResolver.ts @@ -27,6 +27,7 @@ const FAVICON_CANDIDATES = [ "assets/icon.png", "assets/logo.svg", "assets/logo.png", + ".idea/icon.svg", ] as const; // Files that may contain a or icon metadata declaration. From b0b7b38da1dc4b19833d13f84eb907b1e2adfb63 Mon Sep 17 00:00:00 2001 From: Raul Pereira da Silva <38929917+raulpesilva@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:42:19 -0300 Subject: [PATCH 10/20] fix(server): detect localized Windows command errors (#2152) Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com> --- apps/server/src/processRunner.test.ts | 20 +++++++++++++++++++- apps/server/src/processRunner.ts | 15 ++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/server/src/processRunner.test.ts b/apps/server/src/processRunner.test.ts index 15ad4daf09b..af4ff528ee0 100644 --- a/apps/server/src/processRunner.test.ts +++ b/apps/server/src/processRunner.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { runProcess } from "./processRunner.ts"; +import { isWindowsCommandNotFound, runProcess } from "./processRunner.ts"; describe("runProcess", () => { it("fails when output exceeds max buffer in default mode", async () => { @@ -21,3 +21,21 @@ describe("runProcess", () => { expect(result.stderrTruncated).toBe(false); }); }); + +describe("isWindowsCommandNotFound", () => { + it("matches the localized German cmd.exe error text", () => { + const originalPlatform = process.platform; + Object.defineProperty(process, "platform", { value: "win32", configurable: true }); + + try { + expect( + isWindowsCommandNotFound( + 1, + "wird nicht als interner oder externer Befehl, betriebsfahiges Programm oder Batch-Datei erkannt", + ), + ).toBe(true); + } finally { + Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true }); + } + }); +}); diff --git a/apps/server/src/processRunner.ts b/apps/server/src/processRunner.ts index 5402612887d..03b164fc241 100644 --- a/apps/server/src/processRunner.ts +++ b/apps/server/src/processRunner.ts @@ -37,10 +37,23 @@ function normalizeSpawnError(command: string, args: readonly string[], error: un return new Error(`Failed to run ${commandLabel(command, args)}: ${error.message}`); } +const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [ + /is not recognized as an internal or external command/i, + /n.o . reconhecido como um comando interno/i, + /non . riconosciuto come comando interno o esterno/i, + /n.est pas reconnu en tant que commande interne/i, + /no se reconoce como un comando interno o externo/i, + /wird nicht als interner oder externer befehl/i, +] as const; + +function hasWindowsCommandNotFoundMessage(output: string): boolean { + return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output)); +} + export function isWindowsCommandNotFound(code: number | null, stderr: string): boolean { if (process.platform !== "win32") return false; if (code === 9009) return true; - return /is not recognized as an internal or external command/i.test(stderr); + return hasWindowsCommandNotFoundMessage(stderr); } function normalizeExitError( From 8d1d699f88279066f085f95908c845334715830e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Apr 2026 06:35:40 -0700 Subject: [PATCH 11/20] Refactor provider model selections to option arrays (#2246) Co-authored-by: Cursor Agent Co-authored-by: cursor[bot] <206951365+cursor[bot]@users.noreply.github.com> Co-authored-by: codex --- .cursor/.gitignore | 1 + .../git/Layers/ClaudeTextGeneration.test.ts | 21 +- .../src/git/Layers/ClaudeTextGeneration.ts | 40 +- .../git/Layers/CodexTextGeneration.test.ts | 13 +- .../src/git/Layers/CodexTextGeneration.ts | 11 +- .../git/Layers/CursorTextGeneration.test.ts | 13 +- .../src/git/Layers/CursorTextGeneration.ts | 2 +- .../src/git/Layers/OpenCodeTextGeneration.ts | 14 +- .../src/git/Layers/RoutingTextGeneration.ts | 36 +- .../Layers/ProviderCommandReactor.test.ts | 127 +-- .../decider.projectScripts.test.ts | 25 +- apps/server/src/persistence/Migrations.ts | 2 + ..._CanonicalizeModelSelectionOptions.test.ts | 450 ++++++++++ .../026_CanonicalizeModelSelectionOptions.ts | 138 +++ .../src/provider/Layers/ClaudeAdapter.test.ts | 111 +-- .../src/provider/Layers/ClaudeAdapter.ts | 64 +- .../src/provider/Layers/ClaudeProvider.ts | 227 +++-- .../src/provider/Layers/CodexAdapter.test.ts | 23 +- .../src/provider/Layers/CodexAdapter.ts | 27 +- .../src/provider/Layers/CodexProvider.ts | 61 +- .../src/provider/Layers/CursorAdapter.test.ts | 27 +- .../src/provider/Layers/CursorAdapter.ts | 6 +- .../provider/Layers/CursorProvider.test.ts | 195 ++--- .../src/provider/Layers/CursorProvider.ts | 144 ++- .../src/provider/Layers/OpenCodeAdapter.ts | 5 +- .../provider/Layers/OpenCodeProvider.test.ts | 14 +- .../src/provider/Layers/OpenCodeProvider.ts | 71 +- .../Layers/ProviderAdapterRegistry.ts | 13 +- .../provider/Layers/ProviderRegistry.test.ts | 93 +- .../src/provider/Layers/ProviderRegistry.ts | 47 +- .../provider/Layers/ProviderService.test.ts | 22 +- .../src/provider/acp/CursorAcpSupport.test.ts | 10 +- .../src/provider/acp/CursorAcpSupport.ts | 6 +- .../src/provider/builtInProviderCatalog.ts | 49 ++ .../makeManagedServerProvider.test.ts | 28 +- apps/server/src/provider/opencodeRuntime.ts | 1 - .../src/provider/providerSnapshot.test.ts | 28 +- apps/server/src/provider/providerSnapshot.ts | 55 ++ .../src/provider/providerStatusCache.test.ts | 27 +- apps/server/src/serverSettings.test.ts | 99 ++- apps/web/src/components/ChatView.browser.tsx | 127 ++- apps/web/src/components/ChatView.tsx | 13 +- apps/web/src/components/chat/ChatComposer.tsx | 20 +- .../CompactComposerControlsMenu.browser.tsx | 195 +++-- .../chat/ProviderModelPicker.browser.tsx | 258 +++--- .../components/chat/ProviderStatusBanner.tsx | 5 +- .../components/chat/TraitsPicker.browser.tsx | 821 ------------------ apps/web/src/components/chat/TraitsPicker.tsx | 500 ++++------- .../chat/composerProviderRegistry.test.tsx | 516 ----------- .../chat/composerProviderRegistry.tsx | 216 ----- .../chat/composerProviderState.test.tsx | 242 ++++++ .../components/chat/composerProviderState.tsx | 108 +++ .../components/settings/SettingsPanels.tsx | 28 +- apps/web/src/composerDraftStore.test.ts | 141 +-- apps/web/src/composerDraftStore.ts | 399 ++++----- apps/web/src/modelSelection.ts | 47 +- apps/web/src/providerModels.ts | 68 +- packages/contracts/src/model.ts | 180 ++-- packages/contracts/src/orchestration.test.ts | 93 +- packages/contracts/src/orchestration.ts | 15 +- packages/contracts/src/provider.test.ts | 63 +- packages/contracts/src/server.ts | 3 + packages/contracts/src/settings.ts | 37 +- packages/shared/src/model.test.ts | 282 +++--- packages/shared/src/model.ts | 369 ++++---- packages/shared/src/serverSettings.test.ts | 71 +- packages/shared/src/serverSettings.ts | 68 +- 67 files changed, 3403 insertions(+), 3828 deletions(-) create mode 100644 .cursor/.gitignore create mode 100644 apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts create mode 100644 apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.ts create mode 100644 apps/server/src/provider/builtInProviderCatalog.ts delete mode 100644 apps/web/src/components/chat/TraitsPicker.browser.tsx delete mode 100644 apps/web/src/components/chat/composerProviderRegistry.test.tsx delete mode 100644 apps/web/src/components/chat/composerProviderRegistry.tsx create mode 100644 apps/web/src/components/chat/composerProviderState.test.tsx create mode 100644 apps/web/src/components/chat/composerProviderState.tsx diff --git a/.cursor/.gitignore b/.cursor/.gitignore new file mode 100644 index 00000000000..8bf7cc27a18 --- /dev/null +++ b/.cursor/.gitignore @@ -0,0 +1 @@ +plans/ diff --git a/apps/server/src/git/Layers/ClaudeTextGeneration.test.ts b/apps/server/src/git/Layers/ClaudeTextGeneration.test.ts index 08471346989..773f781eed1 100644 --- a/apps/server/src/git/Layers/ClaudeTextGeneration.test.ts +++ b/apps/server/src/git/Layers/ClaudeTextGeneration.test.ts @@ -1,6 +1,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import { Effect, FileSystem, Layer, Path } from "effect"; +import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; import { ServerConfig } from "../../config.ts"; @@ -199,12 +200,10 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGenerationLive", (it) => { stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", modelSelection: { - provider: "claudeAgent", - model: "claude-haiku-4-5", - options: { - thinking: false, - effort: "high", - }, + ...createModelSelection("claudeAgent", "claude-haiku-4-5", [ + { id: "thinking", value: false }, + { id: "effort", value: "high" }, + ]), }, }); @@ -235,12 +234,10 @@ it.layer(ClaudeTextGenerationTestLayer)("ClaudeTextGenerationLive", (it) => { diffSummary: "1 file changed", diffPatch: "diff --git a/README.md b/README.md", modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - fastMode: true, - }, + ...createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ]), }, }); diff --git a/apps/server/src/git/Layers/ClaudeTextGeneration.ts b/apps/server/src/git/Layers/ClaudeTextGeneration.ts index 97e18c3e789..8175dc54b22 100644 --- a/apps/server/src/git/Layers/ClaudeTextGeneration.ts +++ b/apps/server/src/git/Layers/ClaudeTextGeneration.ts @@ -28,10 +28,17 @@ import { sanitizeThreadTitle, toJsonSchemaObject, } from "../Utils.ts"; -import { normalizeClaudeModelOptionsWithCapabilities } from "@t3tools/shared/model"; -import { resolveClaudeApiModelId } from "../../provider/Layers/ClaudeProvider.ts"; +import { + getModelSelectionStringOptionValue, + getProviderOptionDescriptors, +} from "@t3tools/shared/model"; +import { + getClaudeModelCapabilities, + normalizeClaudeCliEffort, + resolveClaudeApiModelId, + resolveClaudeEffort, +} from "../../provider/Layers/ClaudeProvider.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; -import { getClaudeModelCapabilities } from "../../provider/Layers/ClaudeProvider.ts"; const CLAUDE_TIMEOUT_MS = 180_000; @@ -84,15 +91,24 @@ const makeClaudeTextGeneration = Effect.gen(function* () { modelSelection: ClaudeModelSelection; }): Effect.fn.Return { const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson)); - const normalizedOptions = normalizeClaudeModelOptionsWithCapabilities( - getClaudeModelCapabilities(modelSelection.model), - modelSelection.options, - ); + const caps = getClaudeModelCapabilities(modelSelection.model); + const descriptors = getProviderOptionDescriptors({ + caps, + selections: modelSelection.options, + }); + const findDescriptor = (id: string) => descriptors.find((descriptor) => descriptor.id === id); + const rawEffortSelection = getModelSelectionStringOptionValue(modelSelection, "effort"); + const resolvedEffort = resolveClaudeEffort(caps, rawEffortSelection); + const cliEffort = normalizeClaudeCliEffort(resolvedEffort); + const thinkingDescriptor = findDescriptor("thinking"); + const fastModeDescriptor = findDescriptor("fastMode"); + const thinking = + thinkingDescriptor?.type === "boolean" ? thinkingDescriptor.currentValue : undefined; + const fastMode = + fastModeDescriptor?.type === "boolean" ? fastModeDescriptor.currentValue : undefined; const settings = { - ...(typeof normalizedOptions?.thinking === "boolean" - ? { alwaysThinkingEnabled: normalizedOptions.thinking } - : {}), - ...(normalizedOptions?.fastMode ? { fastMode: true } : {}), + ...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}), + ...(fastMode ? { fastMode: true } : {}), }; const claudeSettings = yield* Effect.map( @@ -111,7 +127,7 @@ const makeClaudeTextGeneration = Effect.gen(function* () { jsonSchemaStr, "--model", resolveClaudeApiModelId(modelSelection), - ...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []), + ...(cliEffort ? ["--effort", cliEffort] : []), ...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []), "--dangerously-skip-permissions", ], diff --git a/apps/server/src/git/Layers/CodexTextGeneration.test.ts b/apps/server/src/git/Layers/CodexTextGeneration.test.ts index a07505f025c..2adfcca8483 100644 --- a/apps/server/src/git/Layers/CodexTextGeneration.test.ts +++ b/apps/server/src/git/Layers/CodexTextGeneration.test.ts @@ -1,6 +1,7 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import { Effect, FileSystem, Layer, Path, Result } from "effect"; +import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; import { ServerConfig } from "../../config.ts"; @@ -244,14 +245,10 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGenerationLive", (it) => { branch: "feature/codex-effect", stagedSummary: "M README.md", stagedPatch: "diff --git a/README.md b/README.md", - modelSelection: { - provider: "codex", - model: "gpt-5.4", - options: { - reasoningEffort: "xhigh", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "xhigh" }, + { id: "fastMode", value: true }, + ]), }); }), ), diff --git a/apps/server/src/git/Layers/CodexTextGeneration.ts b/apps/server/src/git/Layers/CodexTextGeneration.ts index 8f15bfa1868..ee1e39d0fa0 100644 --- a/apps/server/src/git/Layers/CodexTextGeneration.ts +++ b/apps/server/src/git/Layers/CodexTextGeneration.ts @@ -30,6 +30,10 @@ import { toJsonSchemaObject, } from "../Utils.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; +import { + getModelSelectionBooleanOptionValue, + getModelSelectionStringOptionValue, +} from "@t3tools/shared/model"; const CODEX_GIT_TEXT_GENERATION_REASONING_EFFORT = "low"; const CODEX_TIMEOUT_MS = 180_000; @@ -155,7 +159,8 @@ const makeCodexTextGeneration = Effect.gen(function* () { const runCodexCommand = Effect.fn("runCodexJson.runCodexCommand")(function* () { const reasoningEffort = - modelSelection.options?.reasoningEffort ?? CODEX_GIT_TEXT_GENERATION_REASONING_EFFORT; + getModelSelectionStringOptionValue(modelSelection, "reasoningEffort") ?? + CODEX_GIT_TEXT_GENERATION_REASONING_EFFORT; const command = ChildProcess.make( codexSettings?.binaryPath || "codex", [ @@ -168,7 +173,9 @@ const makeCodexTextGeneration = Effect.gen(function* () { modelSelection.model, "--config", `model_reasoning_effort="${reasoningEffort}"`, - ...(modelSelection.options?.fastMode ? ["--config", `service_tier="fast"`] : []), + ...(getModelSelectionBooleanOptionValue(modelSelection, "fastMode") === true + ? ["--config", `service_tier="fast"`] + : []), "--output-schema", schemaPath, "--output-last-message", diff --git a/apps/server/src/git/Layers/CursorTextGeneration.test.ts b/apps/server/src/git/Layers/CursorTextGeneration.test.ts index e7bce113474..b8d974fd94d 100644 --- a/apps/server/src/git/Layers/CursorTextGeneration.test.ts +++ b/apps/server/src/git/Layers/CursorTextGeneration.test.ts @@ -6,6 +6,7 @@ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import * as NodeServices from "@effect/platform-node/NodeServices"; import { it } from "@effect/vitest"; import { Effect, Layer } from "effect"; +import { createModelSelection } from "@t3tools/shared/model"; import { expect } from "vitest"; import { ServerSettingsError } from "@t3tools/contracts"; @@ -135,13 +136,11 @@ it.layer(CursorTextGenerationTestLayer)("CursorTextGenerationLive", (it) => { stagedPatch: "diff --git a/apps/server/src/git/Layers/CursorTextGeneration.ts b/apps/server/src/git/Layers/CursorTextGeneration.ts", modelSelection: { - provider: "cursor", - model: "gpt-5.4", - options: { - reasoning: "xhigh", - fastMode: true, - contextWindow: "1m", - }, + ...createModelSelection("cursor", "gpt-5.4", [ + { id: "reasoning", value: "xhigh" }, + { id: "fastMode", value: true }, + { id: "contextWindow", value: "1m" }, + ]), }, }); diff --git a/apps/server/src/git/Layers/CursorTextGeneration.ts b/apps/server/src/git/Layers/CursorTextGeneration.ts index 24f066059c7..6b78728b953 100644 --- a/apps/server/src/git/Layers/CursorTextGeneration.ts +++ b/apps/server/src/git/Layers/CursorTextGeneration.ts @@ -108,7 +108,7 @@ const makeCursorTextGeneration = Effect.gen(function* () { yield* applyCursorAcpModelSelection({ runtime, model: modelSelection.model, - modelOptions: modelSelection.options, + selections: modelSelection.options, mapError: ({ cause, configId, step }) => mapCursorAcpError( operation, diff --git a/apps/server/src/git/Layers/OpenCodeTextGeneration.ts b/apps/server/src/git/Layers/OpenCodeTextGeneration.ts index fd28188d600..d9ebb094e72 100644 --- a/apps/server/src/git/Layers/OpenCodeTextGeneration.ts +++ b/apps/server/src/git/Layers/OpenCodeTextGeneration.ts @@ -7,6 +7,7 @@ import { type OpenCodeModelSelection, } from "@t3tools/contracts"; import { sanitizeBranchFragment, sanitizeFeatureBranchName } from "@t3tools/shared/git"; +import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; import { ServerConfig } from "../../config.ts"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; @@ -320,16 +321,17 @@ const makeOpenCodeTextGeneration = Effect.gen(function* () { if (!session.data) { throw new Error("OpenCode session.create returned no session payload."); } + const selectedAgent = getModelSelectionStringOptionValue(input.modelSelection, "agent"); + const selectedVariant = getModelSelectionStringOptionValue( + input.modelSelection, + "variant", + ); const result = await client.session.prompt({ sessionID: session.data.id, model: parsedModel, - ...(input.modelSelection.options?.agent - ? { agent: input.modelSelection.options.agent } - : {}), - ...(input.modelSelection.options?.variant - ? { variant: input.modelSelection.options.variant } - : {}), + ...(selectedAgent ? { agent: selectedAgent } : {}), + ...(selectedVariant ? { variant: selectedVariant } : {}), parts: [{ type: "text", text: input.prompt }, ...fileParts], }); const info = result.data?.info; diff --git a/apps/server/src/git/Layers/RoutingTextGeneration.ts b/apps/server/src/git/Layers/RoutingTextGeneration.ts index 8f5c166d817..873d616f39a 100644 --- a/apps/server/src/git/Layers/RoutingTextGeneration.ts +++ b/apps/server/src/git/Layers/RoutingTextGeneration.ts @@ -11,11 +11,7 @@ */ import { Effect, Layer, Context } from "effect"; -import { - TextGeneration, - type TextGenerationProvider, - type TextGenerationShape, -} from "../Services/TextGeneration.ts"; +import { TextGeneration, type TextGenerationShape } from "../Services/TextGeneration.ts"; import { CodexTextGenerationLive } from "./CodexTextGeneration.ts"; import { ClaudeTextGenerationLive } from "./ClaudeTextGeneration.ts"; import { CursorTextGenerationLive } from "./CursorTextGeneration.ts"; @@ -46,26 +42,22 @@ class OpenCodeTextGen extends Context.Service - provider === "claudeAgent" - ? claude - : provider === "opencode" - ? openCode - : provider === "cursor" - ? cursor - : codex; + const byProvider = { + codex: yield* CodexTextGen, + claudeAgent: yield* ClaudeTextGen, + cursor: yield* CursorTextGen, + opencode: yield* OpenCodeTextGen, + }; return { generateCommitMessage: (input) => - route(input.modelSelection.provider).generateCommitMessage(input), - generatePrContent: (input) => route(input.modelSelection.provider).generatePrContent(input), - generateBranchName: (input) => route(input.modelSelection.provider).generateBranchName(input), - generateThreadTitle: (input) => route(input.modelSelection.provider).generateThreadTitle(input), + byProvider[input.modelSelection.provider].generateCommitMessage(input), + generatePrContent: (input) => + byProvider[input.modelSelection.provider].generatePrContent(input), + generateBranchName: (input) => + byProvider[input.modelSelection.provider].generateBranchName(input), + generateThreadTitle: (input) => + byProvider[input.modelSelection.provider].generateThreadTitle(input), } satisfies TextGenerationShape; }); diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index dfdfab926f8..a52ed5856e8 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -3,6 +3,7 @@ import os from "node:os"; import path from "node:path"; import type { ModelSelection, ProviderRuntimeEvent, ProviderSession } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { ApprovalRequestId, CommandId, @@ -567,14 +568,10 @@ describe("ProviderCommandReactor", () => { text: "hello fast mode", attachments: [], }, - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -584,25 +581,17 @@ describe("ProviderCommandReactor", () => { await waitFor(() => harness.startSession.mock.calls.length === 1); await waitFor(() => harness.sendTurn.mock.calls.length === 1); expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), }); expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ threadId: ThreadId.make("thread-1"), - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), }); }); @@ -623,13 +612,9 @@ describe("ProviderCommandReactor", () => { text: "hello with effort", attachments: [], }, - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -639,23 +624,15 @@ describe("ProviderCommandReactor", () => { await waitFor(() => harness.startSession.mock.calls.length === 1); await waitFor(() => harness.sendTurn.mock.calls.length === 1); expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), }); expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ threadId: ThreadId.make("thread-1"), - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), }); }); @@ -676,13 +653,9 @@ describe("ProviderCommandReactor", () => { text: "hello with fast mode", attachments: [], }, - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "fastMode", value: true }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -692,23 +665,15 @@ describe("ProviderCommandReactor", () => { await waitFor(() => harness.startSession.mock.calls.length === 1); await waitFor(() => harness.sendTurn.mock.calls.length === 1); expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "fastMode", value: true }, + ]), }); expect(harness.sendTurn.mock.calls[0]?.[0]).toMatchObject({ threadId: ThreadId.make("thread-1"), - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "fastMode", value: true }, + ]), }); }); @@ -916,13 +881,9 @@ describe("ProviderCommandReactor", () => { text: "first claude turn", attachments: [], }, - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "medium", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "medium" }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -943,13 +904,9 @@ describe("ProviderCommandReactor", () => { text: "second claude turn", attachments: [], }, - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -960,13 +917,9 @@ describe("ProviderCommandReactor", () => { await waitFor(() => harness.sendTurn.mock.calls.length === 2); expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ resumeCursor: { opaque: "resume-1" }, - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), }); }); diff --git a/apps/server/src/orchestration/decider.projectScripts.test.ts b/apps/server/src/orchestration/decider.projectScripts.test.ts index a85e21c53f3..1feabb0ed85 100644 --- a/apps/server/src/orchestration/decider.projectScripts.test.ts +++ b/apps/server/src/orchestration/decider.projectScripts.test.ts @@ -6,6 +6,7 @@ import { ProjectId, ThreadId, } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { describe, expect, it } from "vitest"; import { Effect } from "effect"; @@ -162,14 +163,10 @@ describe("decider project scripts", () => { text: "hello", attachments: [], }, - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, runtimeMode: "approval-required", createdAt: now, @@ -191,14 +188,10 @@ describe("decider project scripts", () => { expect(turnStartEvent.payload).toMatchObject({ threadId: ThreadId.make("thread-1"), messageId: asMessageId("message-user-1"), - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), runtimeMode: "approval-required", }); }); diff --git a/apps/server/src/persistence/Migrations.ts b/apps/server/src/persistence/Migrations.ts index 023e3bca051..c8d4647fb72 100644 --- a/apps/server/src/persistence/Migrations.ts +++ b/apps/server/src/persistence/Migrations.ts @@ -38,6 +38,7 @@ import Migration0022 from "./Migrations/022_AuthSessionLastConnectedAt.ts"; import Migration0023 from "./Migrations/023_ProjectionThreadShellSummary.ts"; import Migration0024 from "./Migrations/024_BackfillProjectionThreadShellSummary.ts"; import Migration0025 from "./Migrations/025_CleanupInvalidProjectionPendingApprovals.ts"; +import Migration0026 from "./Migrations/026_CanonicalizeModelSelectionOptions.ts"; /** * Migration loader with all migrations defined inline. @@ -75,6 +76,7 @@ export const migrationEntries = [ [23, "ProjectionThreadShellSummary", Migration0023], [24, "BackfillProjectionThreadShellSummary", Migration0024], [25, "CleanupInvalidProjectionPendingApprovals", Migration0025], + [26, "CanonicalizeModelSelectionOptions", Migration0026], ] as const; export const makeMigrationLoader = (throughId?: number) => diff --git a/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts new file mode 100644 index 00000000000..ffc42521c90 --- /dev/null +++ b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.test.ts @@ -0,0 +1,450 @@ +import { assert, it } from "@effect/vitest"; +import { Effect, Layer } from "effect"; +import * as SqlClient from "effect/unstable/sql/SqlClient"; + +import { runMigrations } from "../Migrations.ts"; +import * as NodeSqliteClient from "../NodeSqliteClient.ts"; + +const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); + +layer("026_CanonicalizeModelSelectionOptions", (it) => { + it.effect("converts legacy object-shape options into array-shape on projections and events", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + + yield* runMigrations({ toMigrationInclusive: 25 }); + + yield* sql` + INSERT INTO projection_projects ( + project_id, + title, + workspace_root, + default_model_selection_json, + scripts_json, + created_at, + updated_at, + deleted_at + ) + VALUES + ( + 'project-legacy', + 'Legacy options project', + '/tmp/legacy', + '{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"max","fastMode":true}}', + '[]', + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL + ), + ( + 'project-no-options', + 'No options project', + '/tmp/no-options', + '{"provider":"codex","model":"gpt-5.4"}', + '[]', + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL + ), + ( + 'project-null-selection', + 'Null model selection project', + '/tmp/null-selection', + NULL, + '[]', + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL + ), + ( + 'project-already-array', + 'Already-canonical options project', + '/tmp/already-array', + '{"provider":"codex","model":"gpt-5.4","options":[{"id":"reasoningEffort","value":"high"}]}', + '[]', + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL + ) + `; + + yield* sql` + INSERT INTO projection_threads ( + thread_id, + project_id, + title, + model_selection_json, + branch, + worktree_path, + latest_turn_id, + created_at, + updated_at, + archived_at, + latest_user_message_at, + pending_approval_count, + pending_user_input_count, + has_actionable_proposed_plan, + deleted_at, + runtime_mode, + interaction_mode + ) + VALUES + ( + 'thread-legacy', + 'project-legacy', + 'Legacy thread', + '{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"max","thinking":false,"contextWindow":"1m"}}', + NULL, NULL, NULL, + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL, NULL, 0, 0, 0, NULL, + 'full-access', 'default' + ), + ( + 'thread-empty-options', + 'project-legacy', + 'Empty options thread', + '{"provider":"codex","model":"gpt-5.4","options":{}}', + NULL, NULL, NULL, + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL, NULL, 0, 0, 0, NULL, + 'full-access', 'default' + ), + ( + 'thread-drop-garbage', + 'project-legacy', + 'Thread with non-scalar entries', + '{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"high","thinking":{"enabled":true,"budgetTokens":2000},"emptyStr":" ","nullish":null}}', + NULL, NULL, NULL, + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL, NULL, 0, 0, 0, NULL, + 'full-access', 'default' + ), + ( + 'thread-no-options', + 'project-legacy', + 'No options thread', + '{"provider":"codex","model":"gpt-5.4"}', + NULL, NULL, NULL, + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL, NULL, 0, 0, 0, NULL, + 'full-access', 'default' + ), + ( + 'thread-already-array', + 'project-legacy', + 'Already array thread', + '{"provider":"codex","model":"gpt-5.4","options":[{"id":"fastMode","value":true}]}', + NULL, NULL, NULL, + '2026-01-01T00:00:00.000Z', + '2026-01-01T00:00:00.000Z', + NULL, NULL, 0, 0, 0, NULL, + 'full-access', 'default' + ) + `; + + yield* sql` + INSERT INTO orchestration_events ( + event_id, + aggregate_kind, + stream_id, + stream_version, + event_type, + occurred_at, + command_id, + causation_event_id, + correlation_id, + actor_kind, + payload_json, + metadata_json + ) + VALUES + ( + 'event-project-created', + 'project', + 'project-legacy', + 1, + 'project.created', + '2026-01-01T00:00:00.000Z', + 'cmd-pc', + NULL, + 'corr-pc', + 'user', + '{"projectId":"project-legacy","title":"Project","workspaceRoot":"/tmp/legacy","defaultModelSelection":{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"max","fastMode":true}},"scripts":[],"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-project-meta-updated', + 'project', + 'project-legacy', + 2, + 'project.meta-updated', + '2026-01-01T00:00:00.000Z', + 'cmd-pmu', + NULL, + 'corr-pmu', + 'user', + '{"projectId":"project-legacy","defaultModelSelection":{"provider":"codex","model":"gpt-5.4","options":{"reasoningEffort":"low"}},"updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-project-null-selection', + 'project', + 'project-legacy', + 3, + 'project.meta-updated', + '2026-01-01T00:00:00.000Z', + 'cmd-null', + NULL, + 'corr-null', + 'user', + '{"projectId":"project-legacy","defaultModelSelection":null,"updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-thread-created', + 'thread', + 'thread-legacy', + 1, + 'thread.created', + '2026-01-01T00:00:00.000Z', + 'cmd-tc', + NULL, + 'corr-tc', + 'user', + '{"threadId":"thread-legacy","projectId":"project-legacy","title":"Thread","modelSelection":{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"max","thinking":false}},"runtimeMode":"full-access","interactionMode":"default","branch":null,"worktreePath":null,"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-thread-meta-updated', + 'thread', + 'thread-legacy', + 2, + 'thread.meta-updated', + '2026-01-01T00:00:00.000Z', + 'cmd-tmu', + NULL, + 'corr-tmu', + 'user', + '{"threadId":"thread-legacy","modelSelection":{"provider":"codex","model":"gpt-5.4","options":{"fastMode":true}},"updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-thread-turn-start', + 'thread', + 'thread-legacy', + 3, + 'thread.turn-start-requested', + '2026-01-01T00:00:00.000Z', + 'cmd-tts', + NULL, + 'corr-tts', + 'user', + '{"threadId":"thread-legacy","messageId":"msg-1","modelSelection":{"provider":"claudeAgent","model":"claude-opus-4-6","options":{"effort":"high","contextWindow":"1m"}},"runtimeMode":"full-access","interactionMode":"default","createdAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-thread-already-array', + 'thread', + 'thread-legacy', + 4, + 'thread.created', + '2026-01-01T00:00:00.000Z', + 'cmd-taa', + NULL, + 'corr-taa', + 'user', + '{"threadId":"thread-already-array","projectId":"project-legacy","title":"Already Array","modelSelection":{"provider":"codex","model":"gpt-5.4","options":[{"id":"reasoningEffort","value":"medium"}]},"runtimeMode":"full-access","interactionMode":"default","branch":null,"worktreePath":null,"createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z"}', + '{}' + ), + ( + 'event-activity-append', + 'thread', + 'thread-legacy', + 5, + 'thread.activity-appended', + '2026-01-01T00:00:00.000Z', + 'cmd-aa', + NULL, + 'corr-aa', + 'user', + '{"threadId":"thread-legacy","activity":{"id":"a","tone":"info","kind":"k","summary":"s","payload":null,"turnId":null,"createdAt":"2026-01-01T00:00:00.000Z"}}', + '{}' + ) + `; + + yield* runMigrations({ toMigrationInclusive: 26 }); + + // Projection projects + const projectRows = yield* sql<{ + readonly projectId: string; + readonly defaultModelSelection: string | null; + }>` + SELECT + project_id AS "projectId", + default_model_selection_json AS "defaultModelSelection" + FROM projection_projects + ORDER BY project_id + `; + assert.deepStrictEqual( + projectRows.map((row) => ({ + projectId: row.projectId, + selection: row.defaultModelSelection ? JSON.parse(row.defaultModelSelection) : null, + })), + [ + { + projectId: "project-already-array", + selection: { + provider: "codex", + model: "gpt-5.4", + options: [{ id: "reasoningEffort", value: "high" }], + }, + }, + { + projectId: "project-legacy", + selection: { + provider: "claudeAgent", + model: "claude-opus-4-6", + options: [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ], + }, + }, + { + projectId: "project-no-options", + selection: { provider: "codex", model: "gpt-5.4" }, + }, + { projectId: "project-null-selection", selection: null }, + ], + ); + + // Projection threads + const threadRows = yield* sql<{ + readonly threadId: string; + readonly modelSelection: string | null; + }>` + SELECT + thread_id AS "threadId", + model_selection_json AS "modelSelection" + FROM projection_threads + ORDER BY thread_id + `; + assert.deepStrictEqual( + threadRows.map((row) => ({ + threadId: row.threadId, + selection: row.modelSelection ? JSON.parse(row.modelSelection) : null, + })), + [ + { + threadId: "thread-already-array", + selection: { + provider: "codex", + model: "gpt-5.4", + options: [{ id: "fastMode", value: true }], + }, + }, + { + threadId: "thread-drop-garbage", + selection: { + provider: "claudeAgent", + model: "claude-opus-4-6", + // Only the scalar string survives; nested object, whitespace + // string, and null are dropped. + options: [{ id: "effort", value: "high" }], + }, + }, + { + threadId: "thread-empty-options", + selection: { provider: "codex", model: "gpt-5.4", options: [] }, + }, + { + threadId: "thread-legacy", + selection: { + provider: "claudeAgent", + model: "claude-opus-4-6", + options: [ + { id: "effort", value: "max" }, + { id: "thinking", value: false }, + { id: "contextWindow", value: "1m" }, + ], + }, + }, + { + threadId: "thread-no-options", + selection: { provider: "codex", model: "gpt-5.4" }, + }, + ], + ); + + // Orchestration events + const eventRows = yield* sql<{ + readonly eventId: string; + readonly payloadJson: string; + }>` + SELECT event_id AS "eventId", payload_json AS "payloadJson" + FROM orchestration_events + ORDER BY event_id + `; + + const payloads = Object.fromEntries( + eventRows.map((row) => [row.eventId, JSON.parse(row.payloadJson)]), + ); + + assert.deepStrictEqual(payloads["event-project-created"].defaultModelSelection, { + provider: "claudeAgent", + model: "claude-opus-4-6", + options: [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ], + }); + + assert.deepStrictEqual(payloads["event-project-meta-updated"].defaultModelSelection, { + provider: "codex", + model: "gpt-5.4", + options: [{ id: "reasoningEffort", value: "low" }], + }); + + assert.strictEqual(payloads["event-project-null-selection"].defaultModelSelection, null); + + assert.deepStrictEqual(payloads["event-thread-created"].modelSelection, { + provider: "claudeAgent", + model: "claude-opus-4-6", + options: [ + { id: "effort", value: "max" }, + { id: "thinking", value: false }, + ], + }); + + assert.deepStrictEqual(payloads["event-thread-meta-updated"].modelSelection, { + provider: "codex", + model: "gpt-5.4", + options: [{ id: "fastMode", value: true }], + }); + + assert.deepStrictEqual(payloads["event-thread-turn-start"].modelSelection, { + provider: "claudeAgent", + model: "claude-opus-4-6", + options: [ + { id: "effort", value: "high" }, + { id: "contextWindow", value: "1m" }, + ], + }); + + // Already-array records are left untouched. + assert.deepStrictEqual(payloads["event-thread-already-array"].modelSelection, { + provider: "codex", + model: "gpt-5.4", + options: [{ id: "reasoningEffort", value: "medium" }], + }); + + // Events with no modelSelection at all are untouched. + assert.isUndefined(payloads["event-activity-append"].modelSelection); + assert.isUndefined(payloads["event-activity-append"].defaultModelSelection); + }), + ); +}); diff --git a/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.ts b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.ts new file mode 100644 index 00000000000..15c08debf64 --- /dev/null +++ b/apps/server/src/persistence/Migrations/026_CanonicalizeModelSelectionOptions.ts @@ -0,0 +1,138 @@ +import * as Effect from "effect/Effect"; +import * as SqlClient from "effect/unstable/sql/SqlClient"; + +/** + * Canonicalize `modelSelection.options` / `defaultModelSelection.options` from + * the legacy object shape (`{ effort: "max", fastMode: true, ... }`) to the + * current array-of-selections shape (`[{ id: "effort", value: "max" }, ...]`). + * + * Migration 016 introduced `modelSelection` with `options` stored as a + * per-provider object. Later the schema was reshaped so that options are a + * generic `Array<{ id, value }>` of user-selected option entries. Stored rows + * from before the reshape still have the object shape and fail to decode. + * + * For each value in the legacy object: + * - string values are kept if non-empty after trim + * - boolean values are always kept (true | false) + * - any other value type (number, null, nested object/array) is dropped, + * matching the permissive client-side normalizer in composerDraftStore. + * + * Touched storage: + * - `projection_threads.model_selection_json.options` + * - `projection_projects.default_model_selection_json.options` + * - `orchestration_events.payload_json.$.modelSelection.options` + * (thread.created | thread.meta-updated | thread.turn-start-requested) + * - `orchestration_events.payload_json.$.defaultModelSelection.options` + * (project.created | project.meta-updated) + */ +export default Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + + yield* sql` + UPDATE projection_threads + SET model_selection_json = json_set( + model_selection_json, + '$.options', + ( + SELECT json_group_array( + json_object( + 'id', key, + 'value', + CASE type + WHEN 'true' THEN json('true') + WHEN 'false' THEN json('false') + ELSE atom + END + ) + ) + FROM json_each(json_extract(model_selection_json, '$.options')) + WHERE (type = 'text' AND trim(coalesce(atom, '')) != '') + OR type IN ('true', 'false') + ) + ) + WHERE model_selection_json IS NOT NULL + AND json_type(model_selection_json, '$.options') = 'object' + `; + + yield* sql` + UPDATE projection_projects + SET default_model_selection_json = json_set( + default_model_selection_json, + '$.options', + ( + SELECT json_group_array( + json_object( + 'id', key, + 'value', + CASE type + WHEN 'true' THEN json('true') + WHEN 'false' THEN json('false') + ELSE atom + END + ) + ) + FROM json_each(json_extract(default_model_selection_json, '$.options')) + WHERE (type = 'text' AND trim(coalesce(atom, '')) != '') + OR type IN ('true', 'false') + ) + ) + WHERE default_model_selection_json IS NOT NULL + AND json_type(default_model_selection_json, '$.options') = 'object' + `; + + yield* sql` + UPDATE orchestration_events + SET payload_json = json_set( + payload_json, + '$.modelSelection.options', + ( + SELECT json_group_array( + json_object( + 'id', key, + 'value', + CASE type + WHEN 'true' THEN json('true') + WHEN 'false' THEN json('false') + ELSE atom + END + ) + ) + FROM json_each(json_extract(payload_json, '$.modelSelection.options')) + WHERE (type = 'text' AND trim(coalesce(atom, '')) != '') + OR type IN ('true', 'false') + ) + ) + WHERE event_type IN ( + 'thread.created', + 'thread.meta-updated', + 'thread.turn-start-requested' + ) + AND json_type(payload_json, '$.modelSelection.options') = 'object' + `; + + yield* sql` + UPDATE orchestration_events + SET payload_json = json_set( + payload_json, + '$.defaultModelSelection.options', + ( + SELECT json_group_array( + json_object( + 'id', key, + 'value', + CASE type + WHEN 'true' THEN json('true') + WHEN 'false' THEN json('false') + ELSE atom + END + ) + ) + FROM json_each(json_extract(payload_json, '$.defaultModelSelection.options')) + WHERE (type = 'text' AND trim(coalesce(atom, '')) != '') + OR type IN ('true', 'false') + ) + ) + WHERE event_type IN ('project.created', 'project.meta-updated') + AND json_type(payload_json, '$.defaultModelSelection.options') = 'object' + `; +}); diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 79c66bdfcf8..07ea11f0d63 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -17,6 +17,7 @@ import { type RuntimeMode, ThreadId, } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { assert, describe, it } from "@effect/vitest"; import { Effect, Fiber, Layer, Random, Stream } from "effect"; @@ -333,13 +334,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "max" }, + ]), runtimeMode: "full-access", }); @@ -380,13 +377,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-7", - options: { - effort: "xhigh", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-7", [ + { id: "effort", value: "xhigh" }, + ]), runtimeMode: "full-access", }); @@ -405,13 +398,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "max" }, + ]), runtimeMode: "full-access", }); @@ -430,13 +419,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-haiku-4-5", - options: { - effort: "high", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-haiku-4-5", [ + { id: "effort", value: "high" }, + ]), runtimeMode: "full-access", }); @@ -455,13 +440,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-haiku-4-5", - options: { - thinking: false, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-haiku-4-5", [ + { id: "thinking", value: false }, + ]), runtimeMode: "full-access", }); @@ -482,13 +463,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - thinking: false, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "thinking", value: false }, + ]), runtimeMode: "full-access", }); @@ -507,13 +484,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "fastMode", value: true }, + ]), runtimeMode: "full-access", }); @@ -534,13 +507,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "fastMode", value: true }, + ]), runtimeMode: "full-access", }); @@ -559,13 +528,9 @@ describe("ClaudeAdapterLive", () => { const session = yield* adapter.startSession({ threadId: THREAD_ID, provider: "claudeAgent", - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "ultrathink", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "ultrathink" }, + ]), runtimeMode: "full-access", }); @@ -573,13 +538,9 @@ describe("ClaudeAdapterLive", () => { threadId: session.threadId, input: "Investigate the edge cases", attachments: [], - modelSelection: { - provider: "claudeAgent", - model: "claude-sonnet-4-6", - options: { - effort: "ultrathink", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "ultrathink" }, + ]), }); const createInput = harness.getLastCreateQueryInput(); @@ -2794,13 +2755,9 @@ describe("ClaudeAdapterLive", () => { yield* adapter.sendTurn({ threadId: session.threadId, input: "hello", - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - contextWindow: "1m", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "contextWindow", value: "1m" }, + ]), attachments: [], }); yield* adapter.sendTurn({ diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 81980acb9b1..03dfffa0426 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -40,9 +40,14 @@ import { ThreadId, TurnId, type UserInputQuestion, - ClaudeAgentEffort, } from "@t3tools/contracts"; -import { applyClaudePromptEffortPrefix, resolveEffort, trimOrNull } from "@t3tools/shared/model"; +import { + applyClaudePromptEffortPrefix, + getModelSelectionBooleanOptionValue, + getModelSelectionStringOptionValue, + getProviderOptionDescriptors, + resolvePromptInjectedEffort, +} from "@t3tools/shared/model"; import { Cause, DateTime, @@ -61,7 +66,12 @@ import { import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; -import { getClaudeModelCapabilities, resolveClaudeApiModelId } from "./ClaudeProvider.ts"; +import { + getClaudeModelCapabilities, + normalizeClaudeCliEffort, + resolveClaudeApiModelId, + resolveClaudeEffort, +} from "./ClaudeProvider.ts"; import { ProviderAdapterProcessError, ProviderAdapterRequestError, @@ -211,19 +221,9 @@ function normalizeClaudeStreamMessages(cause: Cause.Cause): ReadonlyArray return squashed.length > 0 ? [squashed] : []; } -function getEffectiveClaudeAgentEffort( - effort: ClaudeAgentEffort | null | undefined, -): ClaudeSdkEffort | null { - if (!effort) { - return null; - } - if (effort === "ultrathink") { - return null; - } - if (effort === "xhigh") { - return "max"; - } - return effort; +function getEffectiveClaudeAgentEffort(effort: string | null | undefined): ClaudeSdkEffort | null { + const normalized = normalizeClaudeCliEffort(effort); + return normalized ? (normalized as ClaudeSdkEffort) : null; } function isClaudeInterruptedMessage(message: string): boolean { @@ -555,16 +555,14 @@ const CLAUDE_SETTING_SOURCES = [ function buildPromptText(input: ProviderSendTurnInput): string { const rawEffort = - input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.options?.effort : null; + input.modelSelection?.provider === "claudeAgent" + ? getModelSelectionStringOptionValue(input.modelSelection, "effort") + : null; const claudeModel = input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.model : undefined; const caps = getClaudeModelCapabilities(claudeModel); - // For prompt injection, we check if the raw effort is a prompt-injected level (e.g. "ultrathink"). - // resolveEffort strips prompt-injected values (returning the default instead), so we check the raw value directly. - const trimmedEffort = trimOrNull(rawEffort); - const promptEffort = - trimmedEffort && caps.promptInjectedEffortLevels.includes(trimmedEffort) ? trimmedEffort : null; + const promptEffort = resolvePromptInjectedEffort(caps, rawEffort); return applyClaudePromptEffortPrefix(input.input?.trim() ?? "", promptEffort); } @@ -2824,14 +2822,22 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( const modelSelection = input.modelSelection?.provider === "claudeAgent" ? input.modelSelection : undefined; const caps = getClaudeModelCapabilities(modelSelection?.model); + const descriptors = getProviderOptionDescriptors({ caps }); const apiModelId = modelSelection ? resolveClaudeApiModelId(modelSelection) : undefined; - const effort = (resolveEffort(caps, modelSelection?.options?.effort) ?? - null) as ClaudeAgentEffort | null; - const fastMode = modelSelection?.options?.fastMode === true && caps.supportsFastMode; - const thinking = - typeof modelSelection?.options?.thinking === "boolean" && caps.supportsThinkingToggle - ? modelSelection.options.thinking - : undefined; + const rawEffort = getModelSelectionStringOptionValue(modelSelection, "effort"); + const effort = resolveClaudeEffort(caps, rawEffort) ?? null; + const fastModeSupported = descriptors.some( + (descriptor) => descriptor.type === "boolean" && descriptor.id === "fastMode", + ); + const thinkingSupported = descriptors.some( + (descriptor) => descriptor.type === "boolean" && descriptor.id === "thinking", + ); + const fastMode = + getModelSelectionBooleanOptionValue(modelSelection, "fastMode") === true && + fastModeSupported; + const thinking = thinkingSupported + ? getModelSelectionBooleanOptionValue(modelSelection, "thinking") + : undefined; const effectiveEffort = getEffectiveClaudeAgentEffort(effort); const runtimeModeToPermission: Record = { "auto-accept-edits": "acceptEdits", diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 60836210b5d..f41082bbbab 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -11,6 +11,12 @@ import type { import { Cache, Duration, Effect, Equal, Layer, Option, Result, Schema, Stream } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { decodeJsonResult } from "@t3tools/shared/schemaJson"; +import { + createModelCapabilities, + getModelSelectionStringOptionValue, + getProviderOptionCurrentValue, + getProviderOptionDescriptors, +} from "@t3tools/shared/model"; import { query as claudeQuery, type SlashCommand as ClaudeSlashCommand, @@ -18,6 +24,8 @@ import { } from "@anthropic-ai/claude-agent-sdk"; import { + buildBooleanOptionDescriptor, + buildSelectOptionDescriptor, buildServerProvider, AUTH_PROBE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, @@ -35,108 +43,143 @@ import { ClaudeProvider } from "../Services/ClaudeProvider.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import { ServerSettingsError } from "@t3tools/contracts"; -const DEFAULT_CLAUDE_MODEL_CAPABILITIES: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -}; +const DEFAULT_CLAUDE_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [], +}); const PROVIDER = "claudeAgent" as const; +const CLAUDE_PRESENTATION = { + displayName: "Claude", + showInteractionModeToggle: true, +} as const; const MINIMUM_CLAUDE_OPUS_4_7_VERSION = "2.1.111"; const BUILT_IN_MODELS: ReadonlyArray = [ { slug: "claude-opus-4-7", name: "Claude Opus 4.7", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High" }, - { value: "xhigh", label: "Extra High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + buildSelectOptionDescriptor({ + id: "effort", + label: "Reasoning", + options: [ + { value: "low", label: "Low" }, + { value: "medium", label: "Medium" }, + { value: "high", label: "High" }, + { value: "xhigh", label: "Extra High", isDefault: true }, + { value: "max", label: "Max" }, + { value: "ultrathink", label: "Ultrathink" }, + ], + promptInjectedValues: ["ultrathink"], + }), + buildSelectOptionDescriptor({ + id: "contextWindow", + label: "Context Window", + options: [ + { value: "200k", label: "200k", isDefault: true }, + { value: "1m", label: "1M" }, + ], + }), ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, - ], - promptInjectedEffortLevels: ["ultrathink"], - } satisfies ModelCapabilities, + }), }, { slug: "claude-opus-4-6", name: "Claude Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + buildSelectOptionDescriptor({ + id: "effort", + label: "Reasoning", + options: [ + { value: "low", label: "Low" }, + { value: "medium", label: "Medium" }, + { value: "high", label: "High", isDefault: true }, + { value: "max", label: "Max" }, + { value: "ultrathink", label: "Ultrathink" }, + ], + promptInjectedValues: ["ultrathink"], + }), + buildBooleanOptionDescriptor({ + id: "fastMode", + label: "Fast Mode", + }), + buildSelectOptionDescriptor({ + id: "contextWindow", + label: "Context Window", + options: [ + { value: "200k", label: "200k", isDefault: true }, + { value: "1m", label: "1M" }, + ], + }), ], - promptInjectedEffortLevels: ["ultrathink"], - } satisfies ModelCapabilities, + }), }, { slug: "claude-opus-4-5", name: "Claude Opus 4.5", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + buildSelectOptionDescriptor({ + id: "effort", + label: "Reasoning", + options: [ + { value: "low", label: "Low" }, + { value: "medium", label: "Medium" }, + { value: "high", label: "High", isDefault: true }, + { value: "max", label: "Max" }, + ], + }), + buildBooleanOptionDescriptor({ + id: "fastMode", + label: "Fast Mode", + }), ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - } satisfies ModelCapabilities, + }), }, { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + buildSelectOptionDescriptor({ + id: "effort", + label: "Reasoning", + options: [ + { value: "low", label: "Low" }, + { value: "medium", label: "Medium" }, + { value: "high", label: "High", isDefault: true }, + { value: "ultrathink", label: "Ultrathink" }, + ], + promptInjectedValues: ["ultrathink"], + }), + buildSelectOptionDescriptor({ + id: "contextWindow", + label: "Context Window", + options: [ + { value: "200k", label: "200k", isDefault: true }, + { value: "1m", label: "1M" }, + ], + }), ], - promptInjectedEffortLevels: ["ultrathink"], - } satisfies ModelCapabilities, + }), }, { slug: "claude-haiku-4-5", name: "Claude Haiku 4.5", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - } satisfies ModelCapabilities, + capabilities: createModelCapabilities({ + optionDescriptors: [ + buildBooleanOptionDescriptor({ + id: "thinking", + label: "Thinking", + }), + ], + }), }, ]; @@ -166,8 +209,41 @@ export function getClaudeModelCapabilities(model: string | null | undefined): Mo ); } +export function resolveClaudeEffort( + caps: ModelCapabilities, + raw: string | null | undefined, +): string | undefined { + const descriptors = getProviderOptionDescriptors({ + caps, + ...(raw ? { selections: [{ id: "effort", value: raw }] } : {}), + }); + const effortDescriptor = descriptors.find((descriptor) => descriptor.id === "effort"); + const value = getProviderOptionCurrentValue(effortDescriptor); + return typeof value === "string" ? value : undefined; +} + +/** + * Normalize a resolved Claude effort value into one suitable for the Claude + * CLI's `--effort` flag. + * + * Mirrors the mapping used when invoking the Claude Agent SDK + * ({@link getEffectiveClaudeAgentEffort} in ClaudeAdapter): the Opus 4.7 + * capability `"xhigh"` is rewritten to the accepted CLI value `"max"`, and + * `"ultrathink"` is filtered out because it is a prompt-prefix mode rather + * than a CLI-effort value. Returns `undefined` when no flag should be passed. + */ +export function normalizeClaudeCliEffort(effort: string | null | undefined): string | undefined { + if (!effort || effort === "ultrathink") { + return undefined; + } + if (effort === "xhigh") { + return "max"; + } + return effort; +} + export function resolveClaudeApiModelId(modelSelection: ClaudeModelSelection): string { - switch (modelSelection.options?.contextWindow) { + switch (getModelSelectionStringOptionValue(modelSelection, "contextWindow")) { case "1m": return `${modelSelection.model}[1m]`; default: @@ -579,6 +655,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( if (!claudeSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: false, checkedAt, models: allModels, @@ -601,6 +678,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( const error = versionProbe.failure; return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models: allModels, @@ -619,6 +697,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( if (Option.isNone(versionProbe.success)) { return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models: allModels, @@ -639,6 +718,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( const detail = detailFromResult(version); return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models: allModels, @@ -703,6 +783,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( const error = authProbe.failure; return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models, @@ -723,6 +804,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( if (Option.isNone(authProbe.success)) { return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models, @@ -741,6 +823,7 @@ export const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")( const authMetadata = claudeAuthMetadata({ subscriptionType, authMethod }); return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: claudeSettings.enabled, checkedAt, models, @@ -774,6 +857,7 @@ const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProvid if (!claudeSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: false, checkedAt, models, @@ -789,6 +873,7 @@ const makePendingClaudeProvider = (claudeSettings: ClaudeSettings): ServerProvid return buildServerProvider({ provider: PROVIDER, + presentation: CLAUDE_PRESENTATION, enabled: true, checkedAt, models, diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 03d41559345..ad6fddab1ca 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -14,6 +14,7 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { it, vi } from "@effect/vitest"; @@ -242,13 +243,9 @@ validationLayer("CodexAdapterLive validation", (it) => { yield* adapter.startSession({ provider: "codex", threadId: asThreadId("thread-1"), - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "fastMode", value: true }, + ]), runtimeMode: "full-access", }); @@ -309,14 +306,10 @@ sessionErrorLayer("CodexAdapterLive session errors", (it) => { adapter.sendTurn({ threadId: asThreadId("sess-missing"), input: "hello", - modelSelection: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, - }, + modelSelection: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), attachments: [], }), ); diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 0111cd013ce..5c824dc7309 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -26,6 +26,11 @@ import { ChildProcessSpawner } from "effect/unstable/process"; import * as CodexErrors from "effect-codex-app-server/errors"; import * as EffectCodexSchema from "effect-codex-app-server/schema"; +import { + getModelSelectionBooleanOptionValue, + getModelSelectionStringOptionValue, +} from "@t3tools/shared/model"; + import { ProviderAdapterRequestError, ProviderAdapterProcessError, @@ -1372,7 +1377,8 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( ...(input.modelSelection?.provider === "codex" ? { model: input.modelSelection.model } : {}), - ...(input.modelSelection?.provider === "codex" && input.modelSelection.options?.fastMode + ...(input.modelSelection?.provider === "codex" && + getModelSelectionBooleanOptionValue(input.modelSelection, "fastMode") === true ? { serviceTier: "fast" } : {}), }; @@ -1485,19 +1491,26 @@ const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( ); const session = yield* requireSession(input.threadId); + const reasoningEffort = + input.modelSelection?.provider === "codex" + ? getModelSelectionStringOptionValue(input.modelSelection, "reasoningEffort") + : undefined; + const fastMode = + input.modelSelection?.provider === "codex" + ? getModelSelectionBooleanOptionValue(input.modelSelection, "fastMode") + : undefined; return yield* session.runtime .sendTurn({ ...(input.input !== undefined ? { input: input.input } : {}), ...(input.modelSelection?.provider === "codex" ? { model: input.modelSelection.model } : {}), - ...(input.modelSelection?.provider === "codex" && - input.modelSelection.options?.reasoningEffort !== undefined - ? { effort: input.modelSelection.options.reasoningEffort } - : {}), - ...(input.modelSelection?.provider === "codex" && input.modelSelection.options?.fastMode - ? { serviceTier: "fast" } + ...(reasoningEffort + ? { + effort: reasoningEffort as EffectCodexSchema.V2TurnStartParams__ReasoningEffort, + } : {}), + ...(fastMode === true ? { serviceTier: "fast" } : {}), ...(input.interactionMode !== undefined ? { interactionMode: input.interactionMode } : {}), ...(codexAttachments.length > 0 ? { attachments: codexAttachments } : {}), }) diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index 1e48847c52c..915609ca219 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -25,6 +25,8 @@ import type { } from "@t3tools/contracts"; import { ServerSettingsError } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; + import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; import { buildServerProvider } from "../providerSnapshot.ts"; import { CodexProvider } from "../Services/CodexProvider.ts"; @@ -34,6 +36,10 @@ import packageJson from "../../../package.json" with { type: "json" }; const PROVIDER = "codex" as const; const PROVIDER_PROBE_TIMEOUT_MS = 8_000; +const CODEX_PRESENTATION = { + displayName: "Codex", + showInteractionModeToggle: true, +} as const; export interface CodexAppServerProviderSnapshot { readonly account: CodexSchema.V2GetAccountResponse; @@ -87,17 +93,44 @@ function codexAccountAuthLabel(account: CodexSchema.V2GetAccountResponse["accoun function mapCodexModelCapabilities( model: CodexSchema.V2ModelListResponse__Model, ): ModelCapabilities { - return { - reasoningEffortLevels: model.supportedReasoningEfforts.map(({ reasoningEffort }) => ({ - value: reasoningEffort, - label: REASONING_EFFORT_LABELS[reasoningEffort], - ...(reasoningEffort === model.defaultReasoningEffort ? { isDefault: true } : {}), - })), - supportsFastMode: (model.additionalSpeedTiers ?? []).includes("fast"), - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }; + const reasoningOptions = model.supportedReasoningEfforts.map(({ reasoningEffort }) => + reasoningEffort === model.defaultReasoningEffort + ? { + id: reasoningEffort, + label: REASONING_EFFORT_LABELS[reasoningEffort], + isDefault: true, + } + : { + id: reasoningEffort, + label: REASONING_EFFORT_LABELS[reasoningEffort], + }, + ); + const defaultReasoning = reasoningOptions.find((option) => option.isDefault)?.id; + const supportsFastMode = (model.additionalSpeedTiers ?? []).includes("fast"); + return createModelCapabilities({ + optionDescriptors: [ + ...(reasoningOptions.length > 0 + ? [ + { + id: "reasoningEffort", + label: "Reasoning", + type: "select" as const, + options: reasoningOptions, + ...(defaultReasoning ? { currentValue: defaultReasoning } : {}), + }, + ] + : []), + ...(supportsFastMode + ? [ + { + id: "fastMode", + label: "Fast Mode", + type: "boolean" as const, + }, + ] + : []), + ], + }); } const toDisplayName = (model: CodexSchema.V2ModelListResponse__Model): string => { @@ -292,6 +325,7 @@ const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProvider if (!codexSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: false, checkedAt, models, @@ -308,6 +342,7 @@ const makePendingCodexProvider = (codexSettings: CodexSettings): ServerProvider return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: true, checkedAt, models, @@ -375,6 +410,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu if (!codexSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: false, checkedAt, models: emptyModels, @@ -401,6 +437,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu const installed = !Schema.is(CodexErrors.CodexAppServerSpawnError)(error); return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: codexSettings.enabled, checkedAt, models: emptyModels, @@ -420,6 +457,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu if (Option.isNone(probeResult.success)) { return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: codexSettings.enabled, checkedAt, models: emptyModels, @@ -439,6 +477,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu return buildServerProvider({ provider: PROVIDER, + presentation: CODEX_PRESENTATION, enabled: codexSettings.enabled, checkedAt, models: snapshot.models, diff --git a/apps/server/src/provider/Layers/CursorAdapter.test.ts b/apps/server/src/provider/Layers/CursorAdapter.test.ts index e6bbd7569a4..1f619d49f1f 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.test.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.test.ts @@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { Deferred, Effect, Fiber, Layer, Stream } from "effect"; +import { createModelSelection } from "@t3tools/shared/model"; import { ApprovalRequestId, type ProviderRuntimeEvent, ThreadId } from "@t3tools/contracts"; @@ -357,15 +358,11 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => { providers: { cursor: { binaryPath: wrapperPath } }, }); - const modelSelection = { - provider: "cursor" as const, - model: "gpt-5.4", - options: { - reasoning: "xhigh" as const, - contextWindow: "1m", - fastMode: true, - }, - }; + const modelSelection = createModelSelection("cursor", "gpt-5.4", [ + { id: "reasoning", value: "xhigh" }, + { id: "contextWindow", value: "1m" }, + { id: "fastMode", value: true }, + ]); yield* adapter.startSession({ threadId, @@ -1092,7 +1089,9 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => { threadId, input: "second turn after switching model", attachments: [], - modelSelection: { provider: "cursor", model: "composer-2", options: { fastMode: true } }, + modelSelection: createModelSelection("cursor", "composer-2", [ + { id: "fastMode", value: true }, + ]), }); const argvRuns = yield* Effect.promise(() => readArgvLog(argvLogPath)); @@ -1147,14 +1146,18 @@ cursorAdapterTestLayer("CursorAdapterLive", (it) => { threadId, input: "first turn with fast mode", attachments: [], - modelSelection: { provider: "cursor", model: "composer-2", options: { fastMode: true } }, + modelSelection: createModelSelection("cursor", "composer-2", [ + { id: "fastMode", value: true }, + ]), }); yield* adapter.sendTurn({ threadId, input: "second turn without fast mode", attachments: [], - modelSelection: { provider: "cursor", model: "composer-2", options: { fastMode: false } }, + modelSelection: createModelSelection("cursor", "composer-2", [ + { id: "fastMode", value: false }, + ]), }); const requests = yield* Effect.promise(() => readJsonLines(requestLogPath)); diff --git a/apps/server/src/provider/Layers/CursorAdapter.ts b/apps/server/src/provider/Layers/CursorAdapter.ts index b09e0356bfb..5e12bfbefe2 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.ts @@ -7,7 +7,7 @@ import * as nodePath from "node:path"; import { ApprovalRequestId, - type CursorModelOptions, + type ProviderOptionSelection, EventId, type ProviderApprovalDecision, type ProviderInteractionMode, @@ -222,7 +222,7 @@ function applyRequestedSessionConfiguration(input: { readonly modelSelection: | { readonly model: string; - readonly options?: CursorModelOptions | null | undefined; + readonly options?: ReadonlyArray | null | undefined; } | undefined; readonly mapError: (context: { @@ -235,7 +235,7 @@ function applyRequestedSessionConfiguration(input: { yield* applyCursorAcpModelSelection({ runtime: input.runtime, model: input.modelSelection.model, - modelOptions: input.modelSelection.options, + selections: input.modelSelection.options, mapError: ({ cause }) => input.mapError({ cause, diff --git a/apps/server/src/provider/Layers/CursorProvider.test.ts b/apps/server/src/provider/Layers/CursorProvider.test.ts index be90e3c8569..c0ac5d20b0c 100644 --- a/apps/server/src/provider/Layers/CursorProvider.test.ts +++ b/apps/server/src/provider/Layers/CursorProvider.test.ts @@ -8,6 +8,7 @@ import { Effect } from "effect"; import { describe, expect, it } from "vitest"; import type * as EffectAcpSchema from "effect-acp/schema"; import type { CursorSettings, ServerProviderModel } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { buildCursorProviderSnapshot, @@ -27,6 +28,31 @@ import { const __dirname = path.dirname(fileURLToPath(import.meta.url)); const mockAgentPath = path.join(__dirname, "../../../scripts/acp-mock-agent.ts"); +function selectDescriptor( + id: string, + label: string, + options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, +) { + return { + id, + label, + type: "select" as const, + options: [...options], + ...(options.find((option) => option.isDefault)?.id + ? { currentValue: options.find((option) => option.isDefault)?.id } + : {}), + }; +} + +function booleanDescriptor(id: string, label: string, currentValue?: boolean) { + return { + id, + label, + type: "boolean" as const, + ...(typeof currentValue === "boolean" ? { currentValue } : {}), + }; +} + async function makeMockAgentWrapper(extraEnv?: Record) { const dir = await mkdtemp(path.join(os.tmpdir(), "cursor-provider-mock-")); const wrapperPath = path.join(dir, "fake-agent.sh"); @@ -239,13 +265,7 @@ const baseCursorSettings: CursorSettings = { customModels: [], }; -const emptyCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -} as const; +const emptyCapabilities = createModelCapabilities({ optionDescriptors: [] }); describe("getCursorFallbackModels", () => { it("does not publish any built-in cursor models before ACP discovery", () => { @@ -309,52 +329,57 @@ describe("buildCursorProviderSnapshot", () => { describe("buildCursorCapabilitiesFromConfigOptions", () => { it("derives model capabilities from parameterized Cursor ACP config options", () => { - expect(buildCursorCapabilitiesFromConfigOptions(parameterizedGpt54ConfigOptions)).toEqual({ - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium", isDefault: true }, - { value: "high", label: "High" }, - { value: "xhigh", label: "Extra High" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "272k", label: "272K", isDefault: true }, - { value: "1m", label: "1M" }, - ], - promptInjectedEffortLevels: [], - }); + expect(buildCursorCapabilitiesFromConfigOptions(parameterizedGpt54ConfigOptions)).toEqual( + createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoning", "Reasoning", [ + { id: "low", label: "Low" }, + { id: "medium", label: "Medium", isDefault: true }, + { id: "high", label: "High" }, + { id: "xhigh", label: "Extra High" }, + ]), + selectDescriptor("contextWindow", "Context", [ + { id: "272k", label: "272K", isDefault: true }, + { id: "1m", label: "1M" }, + ]), + booleanDescriptor("fastMode", "Fast", false), + ], + }), + ); }); it("detects boolean thinking toggles from model_config options", () => { - expect(buildCursorCapabilitiesFromConfigOptions(parameterizedClaudeConfigOptions)).toEqual({ - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - ], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }); + expect(buildCursorCapabilitiesFromConfigOptions(parameterizedClaudeConfigOptions)).toEqual( + createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoning", "Reasoning", [ + { id: "low", label: "Low" }, + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + ]), + booleanDescriptor("thinking", "Thinking", true), + ], + }), + ); }); it("prefers the newer model_option effort control over legacy thought_level", () => { expect( buildCursorCapabilitiesFromConfigOptions(parameterizedClaudeModelOptionConfigOptions), - ).toEqual({ - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High" }, - { value: "max", label: "Max", isDefault: true }, - ], - supportsFastMode: true, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }); + ).toEqual( + createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoning", "Effort", [ + { id: "low", label: "Low" }, + { id: "medium", label: "Medium" }, + { id: "high", label: "High" }, + { id: "max", label: "Max", isDefault: true }, + ]), + booleanDescriptor("fastMode", "Fast", true), + booleanDescriptor("thinking", "Thinking", true), + ], + }), + ); }); }); @@ -365,73 +390,39 @@ describe("buildCursorDiscoveredModelsFromConfigOptions", () => { slug: "default", name: "Auto", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, { slug: "composer-2", name: "Composer 2", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [booleanDescriptor("fastMode", "Fast", true)], + }), }, { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, { slug: "claude-sonnet-4-6", name: "Sonnet 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, { slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, { slug: "gpt-5.3-codex-spark", name: "Codex 5.3 Spark", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, ]); }); @@ -645,11 +636,11 @@ describe("resolveCursorAcpBaseModelId", () => { describe("resolveCursorAcpConfigUpdates", () => { it("maps Cursor model options onto separate ACP config option updates", () => { expect( - resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, { - reasoning: "xhigh", - fastMode: true, - contextWindow: "1m", - }), + resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, [ + { id: "reasoning", value: "xhigh" }, + { id: "fastMode", value: true }, + { id: "contextWindow", value: "1m" }, + ]), ).toEqual([ { configId: "reasoning", value: "extra-high" }, { configId: "context", value: "1m" }, @@ -659,26 +650,26 @@ describe("resolveCursorAcpConfigUpdates", () => { it("maps boolean thinking toggles when the model exposes them separately", () => { expect( - resolveCursorAcpConfigUpdates(parameterizedClaudeConfigOptions, { - thinking: false, - }), + resolveCursorAcpConfigUpdates(parameterizedClaudeConfigOptions, [ + { id: "thinking", value: false }, + ]), ).toEqual([{ configId: "thinking", value: false }]); }); it("maps explicit fastMode: false so the adapter can clear a prior fast selection", () => { expect( - resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, { - fastMode: false, - }), + resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, [ + { id: "fastMode", value: false }, + ]), ).toEqual([{ configId: "fast", value: "false" }]); }); it("writes Cursor effort changes through the newer model_option config when available", () => { expect( - resolveCursorAcpConfigUpdates(parameterizedClaudeModelOptionConfigOptions, { - reasoning: "max", - thinking: false, - }), + resolveCursorAcpConfigUpdates(parameterizedClaudeModelOptionConfigOptions, [ + { id: "reasoning", value: "max" }, + { id: "thinking", value: false }, + ]), ).toEqual([ { configId: "effort", value: "max" }, { configId: "thinking", value: "false" }, diff --git a/apps/server/src/provider/Layers/CursorProvider.ts b/apps/server/src/provider/Layers/CursorProvider.ts index 70d5656b3ec..4225023f7d8 100644 --- a/apps/server/src/provider/Layers/CursorProvider.ts +++ b/apps/server/src/provider/Layers/CursorProvider.ts @@ -3,9 +3,9 @@ import * as nodeOs from "node:os"; import * as nodePath from "node:path"; import type { - CursorModelOptions, CursorSettings, ModelCapabilities, + ProviderOptionSelection, ServerProvider, ServerProviderAuth, ServerProviderModel, @@ -15,8 +15,15 @@ import type { import type * as EffectAcpSchema from "effect-acp/schema"; import { Cause, Effect, Equal, Exit, Layer, Option, Result, Stream } from "effect"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; +import { + createModelCapabilities, + getProviderOptionBooleanSelectionValue, + getProviderOptionStringSelectionValue, +} from "@t3tools/shared/model"; import { + buildBooleanOptionDescriptor, + buildSelectOptionDescriptor, buildServerProvider, collectStreamAsString, isCommandMissingCause, @@ -29,13 +36,14 @@ import { AcpSessionRuntime } from "../acp/AcpSessionRuntime.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; const PROVIDER = "cursor" as const; -const EMPTY_CAPABILITIES: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -}; +const CURSOR_PRESENTATION = { + displayName: "Cursor", + badgeLabel: "Early Access", + showInteractionModeToggle: true, +} as const; +const EMPTY_CAPABILITIES: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [], +}); const CURSOR_ACP_MODEL_DISCOVERY_TIMEOUT_MS = 15_000; const CURSOR_ACP_MODEL_CAPABILITY_TIMEOUT = "4 seconds"; @@ -55,6 +63,7 @@ function buildInitialCursorProviderSnapshot(cursorSettings: CursorSettings): Ser if (!cursorSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: false, checkedAt, models, @@ -70,6 +79,7 @@ function buildInitialCursorProviderSnapshot(cursorSettings: CursorSettings): Ser return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: true, checkedAt, models, @@ -198,6 +208,28 @@ function isBooleanLikeConfigOption(option: EffectAcpSchema.SessionConfigOption): return values.has("true") && values.has("false"); } +function getBooleanCurrentValue( + option: EffectAcpSchema.SessionConfigOption | undefined, +): boolean | undefined { + if (!option) { + return undefined; + } + if (option.type === "boolean") { + return option.currentValue; + } + if (option.type !== "select") { + return undefined; + } + const normalized = option.currentValue?.trim().toLowerCase(); + if (normalized === "true") { + return true; + } + if (normalized === "false") { + return false; + } + return undefined; +} + export function buildCursorCapabilitiesFromConfigOptions( configOptions: ReadonlyArray | null | undefined, ): ModelCapabilities { @@ -251,14 +283,60 @@ export function buildCursorCapabilitiesFromConfigOptions( const thinkingOption = configOptions.find( (option) => option.category === "model_config" && isCursorThinkingConfigOption(option), ); + const fastCurrentValue = getBooleanCurrentValue(fastOption); + const thinkingCurrentValue = getBooleanCurrentValue(thinkingOption); + const optionDescriptors = [ + ...(reasoningEffortLevels.length > 0 + ? [ + buildSelectOptionDescriptor({ + id: "reasoning", + label: reasoningConfig?.name?.trim() || "Reasoning", + options: reasoningEffortLevels, + }), + ] + : []), + ...(contextWindowOptions.length > 0 + ? [ + buildSelectOptionDescriptor({ + id: "contextWindow", + label: contextOption?.name?.trim() || "Context Window", + options: contextWindowOptions, + }), + ] + : []), + ...(fastOption && isBooleanLikeConfigOption(fastOption) + ? [ + typeof fastCurrentValue === "boolean" + ? buildBooleanOptionDescriptor({ + id: "fastMode", + label: fastOption.name?.trim() || "Fast Mode", + currentValue: fastCurrentValue, + }) + : buildBooleanOptionDescriptor({ + id: "fastMode", + label: fastOption.name?.trim() || "Fast Mode", + }), + ] + : []), + ...(thinkingOption && isBooleanLikeConfigOption(thinkingOption) + ? [ + typeof thinkingCurrentValue === "boolean" + ? buildBooleanOptionDescriptor({ + id: "thinking", + label: thinkingOption.name?.trim() || "Thinking", + currentValue: thinkingCurrentValue, + }) + : buildBooleanOptionDescriptor({ + id: "thinking", + label: thinkingOption.name?.trim() || "Thinking", + }), + ] + : []), + ]; - return { - reasoningEffortLevels, - supportsFastMode: fastOption ? isBooleanLikeConfigOption(fastOption) : false, - supportsThinkingToggle: thinkingOption ? isBooleanLikeConfigOption(thinkingOption) : false, - contextWindowOptions, - promptInjectedEffortLevels: [], - }; + return createModelCapabilities({ + optionDescriptors, + }); } function buildCursorDiscoveredModels( @@ -282,13 +360,7 @@ function buildCursorDiscoveredModels( } function hasCursorModelCapabilities(model: Pick): boolean { - return ( - (model.capabilities?.reasoningEffortLevels.length ?? 0) > 0 || - model.capabilities?.supportsFastMode === true || - model.capabilities?.supportsThinkingToggle === true || - (model.capabilities?.contextWindowOptions.length ?? 0) > 0 || - (model.capabilities?.promptInjectedEffortLevels.length ?? 0) > 0 - ); + return (model.capabilities?.optionDescriptors?.length ?? 0) > 0; } export function buildCursorDiscoveredModelsFromConfigOptions( @@ -387,7 +459,7 @@ export function resolveCursorAcpBaseModelId(model: string | null | undefined): s export function resolveCursorAcpConfigUpdates( configOptions: ReadonlyArray | null | undefined, - modelOptions: CursorModelOptions | null | undefined, + selections: ReadonlyArray | null | undefined, ): ReadonlyArray<{ readonly configId: string; readonly value: string | boolean }> { if (!configOptions || configOptions.length === 0) { return []; @@ -396,7 +468,9 @@ export function resolveCursorAcpConfigUpdates( const updates: Array<{ readonly configId: string; readonly value: string | boolean }> = []; const reasoningOption = findCursorEffortConfigOption(configOptions); - const requestedReasoning = normalizeCursorReasoningValue(modelOptions?.reasoning); + const requestedReasoning = normalizeCursorReasoningValue( + getProviderOptionStringSelectionValue(selections, "reasoning"), + ); if (reasoningOption && requestedReasoning) { const value = findCursorSelectOptionValue(reasoningOption, (option) => { const normalizedValue = normalizeCursorReasoningValue(option.value); @@ -411,14 +485,15 @@ export function resolveCursorAcpConfigUpdates( const contextOption = configOptions.find( (option) => option.category === "model_config" && isCursorContextConfigOption(option), ); - if (contextOption && modelOptions?.contextWindow) { + const requestedContextWindow = getProviderOptionStringSelectionValue(selections, "contextWindow"); + if (contextOption && requestedContextWindow) { const value = findCursorSelectOptionValue( contextOption, (option) => normalizeCursorConfigOptionToken(option.value) === - normalizeCursorConfigOptionToken(modelOptions.contextWindow) || + normalizeCursorConfigOptionToken(requestedContextWindow) || normalizeCursorConfigOptionToken(option.name) === - normalizeCursorConfigOptionToken(modelOptions.contextWindow), + normalizeCursorConfigOptionToken(requestedContextWindow), ); if (value) { updates.push({ configId: contextOption.id, value }); @@ -428,8 +503,9 @@ export function resolveCursorAcpConfigUpdates( const fastOption = configOptions.find( (option) => option.category === "model_config" && isCursorFastConfigOption(option), ); - if (fastOption && typeof modelOptions?.fastMode === "boolean") { - const value = findCursorBooleanConfigValue(fastOption, modelOptions.fastMode); + const requestedFastMode = getProviderOptionBooleanSelectionValue(selections, "fastMode"); + if (fastOption && typeof requestedFastMode === "boolean") { + const value = findCursorBooleanConfigValue(fastOption, requestedFastMode); if (value !== undefined) { updates.push({ configId: fastOption.id, value }); } @@ -438,8 +514,9 @@ export function resolveCursorAcpConfigUpdates( const thinkingOption = configOptions.find( (option) => option.category === "model_config" && isCursorThinkingConfigOption(option), ); - if (thinkingOption && typeof modelOptions?.thinking === "boolean") { - const value = findCursorBooleanConfigValue(thinkingOption, modelOptions.thinking); + const requestedThinking = getProviderOptionBooleanSelectionValue(selections, "thinking"); + if (thinkingOption && typeof requestedThinking === "boolean") { + const value = findCursorBooleanConfigValue(thinkingOption, requestedThinking); if (value !== undefined) { updates.push({ configId: thinkingOption.id, value }); } @@ -610,6 +687,7 @@ export function buildCursorProviderSnapshot(input: { const message = joinProviderMessages(input.parsed.message, input.discoveryWarning); return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: input.cursorSettings.enabled, checkedAt: input.checkedAt, models: providerModelsFromSettings( @@ -962,6 +1040,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( if (!cursorSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: false, checkedAt, models: fallbackModels, @@ -985,6 +1064,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( const error = aboutProbe.failure; return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: cursorSettings.enabled, checkedAt, models: fallbackModels, @@ -1003,6 +1083,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( if (Option.isNone(aboutProbe.success)) { return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: cursorSettings.enabled, checkedAt, models: fallbackModels, @@ -1025,6 +1106,7 @@ export const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")( if (parameterizedModelPickerUnsupportedMessage) { return buildServerProvider({ provider: PROVIDER, + presentation: CURSOR_PRESENTATION, enabled: cursorSettings.enabled, checkedAt, models: fallbackModels, diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index 5081495dcb4..aa504d5b969 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -13,6 +13,7 @@ import { } from "@t3tools/contracts"; import { Cause, Effect, Exit, Layer, Queue, Ref, Scope, Stream } from "effect"; import type { OpencodeClient, Part, PermissionRequest, QuestionRequest } from "@opencode-ai/sdk/v2"; +import { getModelSelectionStringOptionValue } from "@t3tools/shared/model"; import { resolveAttachmentPath } from "../../attachmentStore.ts"; import { ServerConfig } from "../../config.ts"; @@ -1146,11 +1147,11 @@ export function makeOpenCodeAdapterLive(options?: OpenCodeAdapterLiveOptions) { const agent = input.modelSelection?.provider === PROVIDER - ? input.modelSelection.options?.agent + ? getModelSelectionStringOptionValue(input.modelSelection, "agent") : undefined; const variant = input.modelSelection?.provider === PROVIDER - ? input.modelSelection.options?.variant + ? getModelSelectionStringOptionValue(input.modelSelection, "variant") : undefined; context.activeTurnId = turnId; diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts index 4a43c37b686..f32fd6f49e2 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.test.ts @@ -176,14 +176,16 @@ it.layer(makeTestLayer())("OpenCodeProviderLive", (it) => { const model = snapshot.models.find((entry) => entry.slug === "openai/gpt-5.4"); assert.ok(model); - assert.equal( - model.capabilities?.variantOptions?.find((option) => option.isDefault)?.value, - "medium", + const variantDescriptor = model.capabilities?.optionDescriptors?.find( + (descriptor) => descriptor.id === "variant" && descriptor.type === "select", ); - assert.equal( - model.capabilities?.agentOptions?.find((option) => option.isDefault)?.value, - "build", + assert.ok(variantDescriptor && variantDescriptor.type === "select"); + assert.equal(variantDescriptor.options.find((option) => option.isDefault)?.id, "medium"); + const agentDescriptor = model.capabilities?.optionDescriptors?.find( + (descriptor) => descriptor.id === "agent" && descriptor.type === "select", ); + assert.ok(agentDescriptor && agentDescriptor.type === "select"); + assert.equal(agentDescriptor.options.find((option) => option.isDefault)?.id, "build"); }), ); }); diff --git a/apps/server/src/provider/Layers/OpenCodeProvider.ts b/apps/server/src/provider/Layers/OpenCodeProvider.ts index 586bc3d6f48..b94840015ee 100644 --- a/apps/server/src/provider/Layers/OpenCodeProvider.ts +++ b/apps/server/src/provider/Layers/OpenCodeProvider.ts @@ -6,6 +6,8 @@ import type { } from "@t3tools/contracts"; import { Cause, Data, Effect, Equal, Layer, Stream } from "effect"; +import { createModelCapabilities } from "@t3tools/shared/model"; + import { ServerConfig } from "../../config.ts"; import { ServerSettingsService } from "../../serverSettings.ts"; import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; @@ -25,6 +27,10 @@ import { import type { Agent, ProviderListResponse } from "@opencode-ai/sdk/v2"; const PROVIDER = "opencode" as const; +const OPENCODE_PRESENTATION = { + displayName: "OpenCode", + showInteractionModeToggle: false, +} as const; const MINIMUM_OPENCODE_VERSION = "1.14.19"; class OpenCodeProbeError extends Data.TaggedError("OpenCodeProbeError")<{ @@ -159,13 +165,9 @@ function inferDefaultAgent(agents: ReadonlyArray): string | undefined { return agents.find((agent) => agent.name === "build")?.name ?? agents[0]?.name ?? undefined; } -const DEFAULT_OPENCODE_MODEL_CAPABILITIES: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -}; +const DEFAULT_OPENCODE_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [], +}); function openCodeCapabilitiesForModel(input: { readonly providerID: string; @@ -174,27 +176,46 @@ function openCodeCapabilitiesForModel(input: { }): ModelCapabilities { const variantValues = Object.keys(input.model.variants ?? {}); const defaultVariant = inferDefaultVariant(input.providerID, variantValues); - const variantOptions: ModelCapabilities["variantOptions"] = variantValues.map((value) => - Object.assign( - { value, label: titleCaseSlug(value) }, - defaultVariant === value ? { isDefault: true } : {}, - ), + const variantOptions = variantValues.map((value) => + defaultVariant === value + ? { id: value, label: titleCaseSlug(value), isDefault: true as const } + : { id: value, label: titleCaseSlug(value) }, ); const primaryAgents = input.agents.filter( (agent) => !agent.hidden && (agent.mode === "primary" || agent.mode === "all"), ); const defaultAgent = inferDefaultAgent(primaryAgents); - const agentOptions: ModelCapabilities["agentOptions"] = primaryAgents.map((agent) => - Object.assign( - { value: agent.name, label: titleCaseSlug(agent.name) }, - defaultAgent === agent.name ? { isDefault: true } : {}, - ), + const agentOptions = primaryAgents.map((agent) => + defaultAgent === agent.name + ? { id: agent.name, label: titleCaseSlug(agent.name), isDefault: true as const } + : { id: agent.name, label: titleCaseSlug(agent.name) }, ); - return { - ...DEFAULT_OPENCODE_MODEL_CAPABILITIES, - ...(variantOptions.length > 0 ? { variantOptions } : {}), - ...(agentOptions.length > 0 ? { agentOptions } : {}), - }; + return createModelCapabilities({ + optionDescriptors: [ + ...(variantOptions.length > 0 + ? [ + { + id: "variant", + label: "Variant", + type: "select" as const, + options: variantOptions, + ...(defaultVariant ? { currentValue: defaultVariant } : {}), + }, + ] + : []), + ...(agentOptions.length > 0 + ? [ + { + id: "agent", + label: "Agent", + type: "select" as const, + options: agentOptions, + ...(defaultAgent ? { currentValue: defaultAgent } : {}), + }, + ] + : []), + ], + }); } function flattenOpenCodeModels(input: OpenCodeInventory): ReadonlyArray { @@ -242,6 +263,7 @@ const makePendingOpenCodeProvider = (openCodeSettings: OpenCodeSettings): Server if (!openCodeSettings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: false, checkedAt, models, @@ -260,6 +282,7 @@ const makePendingOpenCodeProvider = (openCodeSettings: OpenCodeSettings): Server return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: true, checkedAt, models, @@ -296,6 +319,7 @@ export const OpenCodeProviderLive = Layer.effect( }); return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: input.settings.enabled, checkedAt, models: providerModelsFromSettings( @@ -317,6 +341,7 @@ export const OpenCodeProviderLive = Layer.effect( if (!input.settings.enabled) { return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: false, checkedAt, models: providerModelsFromSettings( @@ -368,6 +393,7 @@ export const OpenCodeProviderLive = Layer.effect( if (compareCliVersions(version, MINIMUM_OPENCODE_VERSION) < 0) { return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: input.settings.enabled, checkedAt, models: providerModelsFromSettings( @@ -433,6 +459,7 @@ export const OpenCodeProviderLive = Layer.effect( const connectedCount = inventoryExit.value.providerList.connected.length; return buildServerProvider({ provider: PROVIDER, + presentation: OPENCODE_PRESENTATION, enabled: true, checkedAt, models, diff --git a/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts b/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts index 22fc0b7fda5..dabe5b8581e 100644 --- a/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts +++ b/apps/server/src/provider/Layers/ProviderAdapterRegistry.ts @@ -19,6 +19,7 @@ import { ClaudeAdapter } from "../Services/ClaudeAdapter.ts"; import { CodexAdapter } from "../Services/CodexAdapter.ts"; import { CursorAdapter } from "../Services/CursorAdapter.ts"; import { OpenCodeAdapter } from "../Services/OpenCodeAdapter.ts"; +import { createBuiltInAdapterList } from "../builtInProviderCatalog.ts"; export interface ProviderAdapterRegistryLiveOptions { readonly adapters?: ReadonlyArray>; @@ -31,12 +32,12 @@ const makeProviderAdapterRegistry = Effect.fn("makeProviderAdapterRegistry")(fun const adapters = options?.adapters !== undefined ? options.adapters - : [ - yield* CodexAdapter, - yield* ClaudeAdapter, - yield* OpenCodeAdapter, - ...(cursorAdapterOption._tag === "Some" ? [cursorAdapterOption.value] : []), - ]; + : createBuiltInAdapterList({ + codex: yield* CodexAdapter, + claudeAgent: yield* ClaudeAdapter, + opencode: yield* OpenCodeAdapter, + ...(cursorAdapterOption._tag === "Some" ? { cursor: cursorAdapterOption.value } : {}), + }); const byProvider = new Map(adapters.map((adapter) => [adapter.provider, adapter])); const getByProvider: ProviderAdapterRegistryShape["getByProvider"] = (provider) => { diff --git a/apps/server/src/provider/Layers/ProviderRegistry.test.ts b/apps/server/src/provider/Layers/ProviderRegistry.test.ts index 8fe28351a41..1df9da67743 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.test.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.test.ts @@ -11,6 +11,7 @@ import { import * as PlatformError from "effect/PlatformError"; import { ChildProcessSpawner } from "effect/unstable/process"; import { deepMerge } from "@t3tools/shared/Struct"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { checkCodexProviderStatus, type CodexAppServerProviderSnapshot } from "./CodexProvider.ts"; import { checkClaudeProviderStatus, parseClaudeAuthStatusFromOutput } from "./ClaudeProvider.ts"; @@ -29,6 +30,30 @@ process.env.T3CODE_CURSOR_ENABLED = "1"; const encoder = new TextEncoder(); +function selectDescriptor( + id: string, + label: string, + options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, +) { + return { + id, + label, + type: "select" as const, + options: [...options], + ...(options.find((option) => option.isDefault)?.id + ? { currentValue: options.find((option) => option.isDefault)?.id } + : {}), + }; +} + +function booleanDescriptor(id: string, label: string) { + return { + id, + label, + type: "boolean" as const, + }; +} + function mockHandle(result: { stdout: string; stderr: string; code: number }) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), @@ -88,16 +113,15 @@ function failingSpawnerLayer(description: string) { ); } -const codexModelCapabilities = { - reasoningEffortLevels: [ - { value: "high", label: "High", isDefault: true }, - { value: "low", label: "Low" }, +const codexModelCapabilities = createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "high", label: "High", isDefault: true }, + { id: "low", label: "Low" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -} satisfies NonNullable; +}) satisfies NonNullable; function makeCodexProbeSnapshot( input: Partial = {}, @@ -330,13 +354,15 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))( slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [{ value: "high", label: "High", isDefault: true }], - supportsFastMode: true, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoning", "Reasoning", [ + { id: "high", label: "High", isDefault: true }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + booleanDescriptor("thinking", "Thinking"), + ], + }), }, ], slashCommands: [], @@ -367,13 +393,15 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))( slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [{ value: "high", label: "High", isDefault: true }], - supportsFastMode: true, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoning", "Reasoning", [ + { id: "high", label: "High", isDefault: true }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + booleanDescriptor("thinking", "Thinking"), + ], + }), }, ], slashCommands: [], @@ -387,13 +415,9 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))( slug: "claude-opus-4-6", name: "Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [], + }), }, ], } satisfies ServerProvider; @@ -603,9 +627,14 @@ it.layer(Layer.mergeAll(NodeServices.layer, ServerSettingsService.layerTest()))( "Expected Claude Opus 4.7 capabilities to be present for Claude Code v2.1.111.", ); } + const effortDescriptor = opus47.capabilities.optionDescriptors?.find( + (descriptor) => descriptor.type === "select" && descriptor.id === "effort", + ); assert.deepStrictEqual( - opus47.capabilities.reasoningEffortLevels.find((level) => level.isDefault), - { value: "xhigh", label: "Extra High", isDefault: true }, + effortDescriptor?.type === "select" + ? effortDescriptor.options.find((option) => option.isDefault) + : undefined, + { id: "xhigh", label: "Extra High", isDefault: true }, ); }).pipe( Effect.provide( diff --git a/apps/server/src/provider/Layers/ProviderRegistry.ts b/apps/server/src/provider/Layers/ProviderRegistry.ts index 3f83419a389..ddd1701a94f 100644 --- a/apps/server/src/provider/Layers/ProviderRegistry.ts +++ b/apps/server/src/provider/Layers/ProviderRegistry.ts @@ -25,13 +25,8 @@ import { resolveProviderStatusCachePath, writeProviderStatusCache, } from "../providerStatusCache.ts"; - -type ProviderSnapshotSource = { - readonly provider: ProviderKind; - readonly getSnapshot: Effect.Effect; - readonly refresh: Effect.Effect; - readonly streamChanges: Stream.Stream; -}; +import { createBuiltInProviderSources } from "../builtInProviderCatalog.ts"; +import type { ProviderSnapshotSource } from "../builtInProviderCatalog.ts"; const loadProviders = ( providerSources: ReadonlyArray, @@ -41,11 +36,7 @@ const loadProviders = ( }); const hasModelCapabilities = (model: ServerProvider["models"][number]): boolean => - (model.capabilities?.reasoningEffortLevels.length ?? 0) > 0 || - model.capabilities?.supportsFastMode === true || - model.capabilities?.supportsThinkingToggle === true || - (model.capabilities?.contextWindowOptions.length ?? 0) > 0 || - (model.capabilities?.promptInjectedEffortLevels.length ?? 0) > 0; + (model.capabilities?.optionDescriptors?.length ?? 0) > 0; const mergeProviderModels = ( previousModels: ReadonlyArray, @@ -98,32 +89,12 @@ const ProviderRegistryLiveBase = Layer.effect( const cursorProvider = yield* CursorProvider; - const providerSources = [ - { - provider: "codex", - getSnapshot: codexProvider.getSnapshot, - refresh: codexProvider.refresh, - streamChanges: codexProvider.streamChanges, - }, - { - provider: "claudeAgent", - getSnapshot: claudeProvider.getSnapshot, - refresh: claudeProvider.refresh, - streamChanges: claudeProvider.streamChanges, - }, - { - provider: "opencode", - getSnapshot: openCodeProvider.getSnapshot, - refresh: openCodeProvider.refresh, - streamChanges: openCodeProvider.streamChanges, - }, - { - provider: "cursor", - getSnapshot: cursorProvider.getSnapshot, - refresh: cursorProvider.refresh, - streamChanges: cursorProvider.streamChanges, - }, - ] satisfies ReadonlyArray; + const providerSources = createBuiltInProviderSources({ + codex: codexProvider, + claudeAgent: claudeProvider, + opencode: openCodeProvider, + cursor: cursorProvider, + }) satisfies ReadonlyArray; const activeProviders = PROVIDER_CACHE_IDS; const changesPubSub = yield* Effect.acquireRelease( PubSub.unbounded>(), diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index 089ae9da990..d7b8c1cb38a 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -17,6 +17,7 @@ import { ThreadId, TurnId, } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { it, assert, vi } from "@effect/vitest"; import { Effect, Fiber, Layer, Metric, Option, PubSub, Ref, Stream } from "effect"; @@ -860,13 +861,9 @@ routing.layer("ProviderServiceLive routing", (it) => { provider: "claudeAgent", threadId: asThreadId("thread-claude-send-turn"), cwd: "/tmp/project-claude-send-turn", - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "max" }, + ]), runtimeMode: "full-access", }); @@ -893,13 +890,10 @@ routing.layer("ProviderServiceLive routing", (it) => { }; assert.equal(startPayload.provider, "claudeAgent"); assert.equal(startPayload.cwd, "/tmp/project-claude-send-turn"); - assert.deepEqual(startPayload.modelSelection, { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - }, - }); + assert.deepEqual( + startPayload.modelSelection, + createModelSelection("claudeAgent", "claude-opus-4-6", [{ id: "effort", value: "max" }]), + ); assert.deepEqual(startPayload.resumeCursor, initial.resumeCursor); assert.equal(startPayload.threadId, initial.threadId); } diff --git a/apps/server/src/provider/acp/CursorAcpSupport.test.ts b/apps/server/src/provider/acp/CursorAcpSupport.test.ts index 94de569b2b2..30941acbfd5 100644 --- a/apps/server/src/provider/acp/CursorAcpSupport.test.ts +++ b/apps/server/src/provider/acp/CursorAcpSupport.test.ts @@ -99,11 +99,11 @@ describe("applyCursorAcpModelSelection", () => { applyCursorAcpModelSelection({ runtime, model: "gpt-5.4-medium-fast[reasoning=medium,context=272k]", - modelOptions: { - reasoning: "xhigh", - contextWindow: "1m", - fastMode: true, - }, + selections: [ + { id: "reasoning", value: "xhigh" }, + { id: "contextWindow", value: "1m" }, + { id: "fastMode", value: true }, + ], mapError: ({ step, configId, cause }) => new Error( step === "set-config-option" diff --git a/apps/server/src/provider/acp/CursorAcpSupport.ts b/apps/server/src/provider/acp/CursorAcpSupport.ts index 72b9af394b3..25e342f0d04 100644 --- a/apps/server/src/provider/acp/CursorAcpSupport.ts +++ b/apps/server/src/provider/acp/CursorAcpSupport.ts @@ -1,4 +1,4 @@ -import { type CursorModelOptions, type CursorSettings } from "@t3tools/contracts"; +import { type CursorSettings, type ProviderOptionSelection } from "@t3tools/contracts"; import { Effect, Layer, Scope } from "effect"; import { ChildProcessSpawner } from "effect/unstable/process"; import type * as EffectAcpErrors from "effect-acp/errors"; @@ -76,7 +76,7 @@ interface CursorAcpModelSelectionRuntime { export function applyCursorAcpModelSelection(input: { readonly runtime: CursorAcpModelSelectionRuntime; readonly model: string | null | undefined; - readonly modelOptions: CursorModelOptions | null | undefined; + readonly selections: ReadonlyArray | null | undefined; readonly mapError: (context: CursorAcpModelSelectionErrorContext) => E; }): Effect.Effect { return Effect.gen(function* () { @@ -91,7 +91,7 @@ export function applyCursorAcpModelSelection(input: { const configUpdates = resolveCursorAcpConfigUpdates( yield* input.runtime.getConfigOptions, - input.modelOptions, + input.selections, ); for (const update of configUpdates) { yield* input.runtime.setConfigOption(update.configId, update.value).pipe( diff --git a/apps/server/src/provider/builtInProviderCatalog.ts b/apps/server/src/provider/builtInProviderCatalog.ts new file mode 100644 index 00000000000..1fd10a85d9d --- /dev/null +++ b/apps/server/src/provider/builtInProviderCatalog.ts @@ -0,0 +1,49 @@ +import type { ProviderKind, ServerProvider } from "@t3tools/contracts"; +import type { Stream } from "effect"; +import type { ProviderAdapterError } from "./Errors.ts"; +import type { ProviderAdapterShape } from "./Services/ProviderAdapter.ts"; +import type { ServerProviderShape } from "./Services/ServerProvider.ts"; + +export type ProviderSnapshotSource = { + readonly provider: ProviderKind; + readonly getSnapshot: ServerProviderShape["getSnapshot"]; + readonly refresh: ServerProviderShape["refresh"]; + readonly streamChanges: Stream.Stream; +}; + +type BuiltInProviderServiceMap = Record; +type BuiltInAdapterMap = { + readonly codex: ProviderAdapterShape; + readonly claudeAgent: ProviderAdapterShape; + readonly opencode: ProviderAdapterShape; + readonly cursor?: ProviderAdapterShape; +}; + +export const BUILT_IN_PROVIDER_ORDER = [ + "codex", + "claudeAgent", + "opencode", + "cursor", +] as const satisfies ReadonlyArray; + +export function createBuiltInProviderSources( + services: BuiltInProviderServiceMap, +): ReadonlyArray { + return BUILT_IN_PROVIDER_ORDER.map((provider) => ({ + provider, + getSnapshot: services[provider].getSnapshot, + refresh: services[provider].refresh, + streamChanges: services[provider].streamChanges, + })); +} + +export function createBuiltInAdapterList( + adapters: BuiltInAdapterMap, +): ReadonlyArray> { + return [ + adapters.codex, + adapters.claudeAgent, + adapters.opencode, + ...(adapters.cursor ? [adapters.cursor] : []), + ]; +} diff --git a/apps/server/src/provider/makeManagedServerProvider.test.ts b/apps/server/src/provider/makeManagedServerProvider.test.ts index 31fe73a467e..5d50d12e372 100644 --- a/apps/server/src/provider/makeManagedServerProvider.test.ts +++ b/apps/server/src/provider/makeManagedServerProvider.test.ts @@ -1,9 +1,21 @@ import { describe, it, assert } from "@effect/vitest"; import type { ServerProvider } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { Deferred, Effect, Fiber, PubSub, Ref, Stream } from "effect"; import { makeManagedServerProvider } from "./makeManagedServerProvider.ts"; +const emptyCapabilities = createModelCapabilities({ optionDescriptors: [] }); +const fastModeCapabilities = createModelCapabilities({ + optionDescriptors: [ + { + id: "fastMode", + label: "Fast Mode", + type: "boolean", + }, + ], +}); + interface TestSettings { readonly enabled: boolean; } @@ -43,13 +55,7 @@ const enrichedSnapshot: ServerProvider = { slug: "composer-2", name: "Composer 2", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: fastModeCapabilities, }, ], }; @@ -68,13 +74,7 @@ const enrichedSnapshotSecond: ServerProvider = { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, ], }; diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index 9f10738dbfb..41ec5102c3c 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -36,7 +36,6 @@ import { NetService } from "@t3tools/shared/Net"; const OPENCODE_SERVER_READY_PREFIX = "opencode server listening"; const DEFAULT_OPENCODE_SERVER_TIMEOUT_MS = 5_000; const DEFAULT_HOSTNAME = "127.0.0.1"; - export interface OpenCodeServerProcess { readonly url: string; readonly exitCode: Effect.Effect; diff --git a/apps/server/src/provider/providerSnapshot.test.ts b/apps/server/src/provider/providerSnapshot.test.ts index 0a0d31ccb59..f990afb2d61 100644 --- a/apps/server/src/provider/providerSnapshot.test.ts +++ b/apps/server/src/provider/providerSnapshot.test.ts @@ -1,17 +1,27 @@ import { describe, expect, it } from "vitest"; import type { ModelCapabilities } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { providerModelsFromSettings } from "./providerSnapshot.ts"; -const OPENCODE_CUSTOM_MODEL_CAPABILITIES: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - variantOptions: [{ value: "medium", label: "Medium", isDefault: true }], - agentOptions: [{ value: "build", label: "Build", isDefault: true }], -}; +const OPENCODE_CUSTOM_MODEL_CAPABILITIES: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [ + { + id: "variant", + label: "Reasoning", + type: "select", + options: [{ id: "medium", label: "Medium", isDefault: true }], + currentValue: "medium", + }, + { + id: "agent", + label: "Agent", + type: "select", + options: [{ id: "build", label: "Build", isDefault: true }], + currentValue: "build", + }, + ], +}); describe("providerModelsFromSettings", () => { it("applies the provided capabilities to custom models", () => { diff --git a/apps/server/src/provider/providerSnapshot.ts b/apps/server/src/provider/providerSnapshot.ts index fcea249e6ef..f1f03074852 100644 --- a/apps/server/src/provider/providerSnapshot.ts +++ b/apps/server/src/provider/providerSnapshot.ts @@ -30,6 +30,12 @@ export interface ProviderProbeResult { readonly message?: string; } +export interface ServerProviderPresentation { + readonly displayName: string; + readonly badgeLabel?: string; + readonly showInteractionModeToggle?: boolean; +} + export function nonEmptyTrimmed(value: string | undefined): string | undefined { if (!value) return undefined; const trimmed = value.trim(); @@ -129,8 +135,52 @@ export function providerModelsFromSettings( return [...resolvedBuiltInModels, ...customEntries]; } +export function buildSelectOptionDescriptor(input: { + readonly id: string; + readonly label: string; + readonly options: + | ReadonlyArray<{ value: string; label: string; isDefault?: boolean | undefined }> + | undefined; + readonly description?: string; + readonly promptInjectedValues?: ReadonlyArray; +}) { + const options = (input.options ?? []).map((option) => + option.isDefault + ? { id: option.value, label: option.label, isDefault: true } + : { id: option.value, label: option.label }, + ); + const currentValue = options.find((option) => option.isDefault)?.id; + return { + id: input.id, + label: input.label, + type: "select" as const, + options, + ...(currentValue ? { currentValue } : {}), + ...(input.description ? { description: input.description } : {}), + ...(input.promptInjectedValues && input.promptInjectedValues.length > 0 + ? { promptInjectedValues: [...input.promptInjectedValues] } + : {}), + }; +} + +export function buildBooleanOptionDescriptor(input: { + readonly id: string; + readonly label: string; + readonly currentValue?: boolean; + readonly description?: string; +}) { + return { + id: input.id, + label: input.label, + type: "boolean" as const, + ...(input.description ? { description: input.description } : {}), + ...(typeof input.currentValue === "boolean" ? { currentValue: input.currentValue } : {}), + }; +} + export function buildServerProvider(input: { provider: ServerProvider["provider"]; + presentation: ServerProviderPresentation; enabled: boolean; checkedAt: string; models: ReadonlyArray; @@ -140,6 +190,11 @@ export function buildServerProvider(input: { }): ServerProvider { return { provider: input.provider, + displayName: input.presentation.displayName, + ...(input.presentation.badgeLabel ? { badgeLabel: input.presentation.badgeLabel } : {}), + ...(typeof input.presentation.showInteractionModeToggle === "boolean" + ? { showInteractionModeToggle: input.presentation.showInteractionModeToggle } + : {}), enabled: input.enabled, installed: input.probe.installed, version: input.probe.version, diff --git a/apps/server/src/provider/providerStatusCache.test.ts b/apps/server/src/provider/providerStatusCache.test.ts index b0cb5bc663c..5a81341f6d5 100644 --- a/apps/server/src/provider/providerStatusCache.test.ts +++ b/apps/server/src/provider/providerStatusCache.test.ts @@ -1,5 +1,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import type { ServerProvider } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { assert, it } from "@effect/vitest"; import { Effect, FileSystem } from "effect"; @@ -10,6 +11,8 @@ import { writeProviderStatusCache, } from "./providerStatusCache.ts"; +const emptyCapabilities = createModelCapabilities({ optionDescriptors: [] }); + const makeProvider = ( provider: ServerProvider["provider"], overrides?: Partial, @@ -81,13 +84,7 @@ it.layer(NodeServices.layer)("providerStatusCache", (it) => { slug: "gpt-5-mini", name: "GPT-5 Mini", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, ], message: "Cached message", @@ -106,13 +103,7 @@ it.layer(NodeServices.layer)("providerStatusCache", (it) => { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, ], message: "Pending refresh", @@ -131,13 +122,7 @@ it.layer(NodeServices.layer)("providerStatusCache", (it) => { slug: "gpt-5-mini", name: "GPT-5 Mini", isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: emptyCapabilities, }, ], installed: cachedCodex.installed, diff --git a/apps/server/src/serverSettings.test.ts b/apps/server/src/serverSettings.test.ts index 655ede9441f..55f598054be 100644 --- a/apps/server/src/serverSettings.test.ts +++ b/apps/server/src/serverSettings.test.ts @@ -1,5 +1,6 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; -import { DEFAULT_SERVER_SETTINGS, ServerSettingsPatch } from "@t3tools/contracts"; +import { DEFAULT_SERVER_SETTINGS, ServerSettings, ServerSettingsPatch } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; import { assert, it } from "@effect/vitest"; import { Effect, FileSystem, Layer, Schema } from "effect"; import { ServerConfig } from "./config.ts"; @@ -28,22 +29,40 @@ it.layer(NodeServices.layer)("server settings", (it) => { assert.deepEqual( decodePatch({ textGenerationModelSelection: { - options: { - fastMode: false, - }, + options: [{ id: "fastMode", value: false }], }, }), { textGenerationModelSelection: { - options: { - fastMode: false, - }, + options: [{ id: "fastMode", value: false }], }, }, ); }), ); + it.effect( + "decodes legacy object-shaped textGenerationModelSelection.options from settings.json", + () => + Effect.sync(() => { + const decode = Schema.decodeUnknownSync(ServerSettings); + + const decoded = decode({ + textGenerationModelSelection: { + provider: "codex", + model: "gpt-5.4-mini", + options: { reasoningEffort: "low" }, + }, + }); + + assert.deepEqual(decoded.textGenerationModelSelection, { + provider: "codex", + model: "gpt-5.4-mini", + options: [{ id: "reasoningEffort", value: "low" }], + }); + }), + ); + it.effect("deep merges nested settings updates without dropping siblings", () => Effect.gen(function* () { const serverSettings = yield* ServerSettingsService; @@ -62,10 +81,14 @@ it.layer(NodeServices.layer)("server settings", (it) => { textGenerationModelSelection: { provider: "codex", model: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, - options: { - reasoningEffort: "high", - fastMode: true, - }, + options: createModelSelection( + "codex", + DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, + [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], + ).options!, }, }); @@ -76,9 +99,7 @@ it.layer(NodeServices.layer)("server settings", (it) => { }, }, textGenerationModelSelection: { - options: { - fastMode: false, - }, + options: [{ id: "fastMode", value: false }], }, }); @@ -94,14 +115,13 @@ it.layer(NodeServices.layer)("server settings", (it) => { customModels: ["claude-custom"], launchArgs: "", }); - assert.deepEqual(next.textGenerationModelSelection, { - provider: "codex", - model: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, - options: { - reasoningEffort: "high", - fastMode: false, - }, - }); + assert.deepEqual( + next.textGenerationModelSelection, + createModelSelection("codex", DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: false }, + ]), + ); }).pipe(Effect.provide(makeServerSettingsLayer())), ); @@ -114,9 +134,9 @@ it.layer(NodeServices.layer)("server settings", (it) => { textGenerationModelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6", - options: { - effort: "high", - }, + options: createModelSelection("claudeAgent", "claude-sonnet-4-6", [ + { id: "effort", value: "high" }, + ]).options!, }, }); @@ -126,19 +146,16 @@ it.layer(NodeServices.layer)("server settings", (it) => { textGenerationModelSelection: { provider: "codex", model: "gpt-5.4", - options: { - reasoningEffort: "high", - }, + options: createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "high" }, + ]).options!, }, }); - assert.deepEqual(next.textGenerationModelSelection, { - provider: "codex", - model: "gpt-5.4", - options: { - reasoningEffort: "high", - }, - }); + assert.deepEqual( + next.textGenerationModelSelection, + createModelSelection("codex", "gpt-5.4", [{ id: "reasoningEffort", value: "high" }]), + ); }).pipe(Effect.provide(makeServerSettingsLayer())), ); @@ -150,10 +167,14 @@ it.layer(NodeServices.layer)("server settings", (it) => { textGenerationModelSelection: { provider: "codex", model: DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, - options: { - reasoningEffort: "high", - fastMode: true, - }, + options: createModelSelection( + "codex", + DEFAULT_SERVER_SETTINGS.textGenerationModelSelection.model, + [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], + ).options!, }, }); diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index 6857a51b519..f5cc79b88a8 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -18,6 +18,7 @@ import { DEFAULT_SERVER_SETTINGS, } from "@t3tools/contracts"; import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; +import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model"; import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; import { HttpResponse, http, ws } from "msw"; import { setupWorker } from "msw/browser"; @@ -3777,14 +3778,10 @@ describe("ChatView timeline estimator parity (full app)", () => { it("snapshots sticky codex settings into a new draft thread", async () => { useComposerDraftStore.setState({ stickyModelSelectionByProvider: { - codex: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "medium", - fastMode: true, - }, - }, + codex: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "medium" }, + { id: "fastMode", value: true }, + ]), }, stickyActiveProvider: "codex", }); @@ -3810,14 +3807,16 @@ describe("ChatView timeline estimator parity (full app)", () => { ); const newDraftId = draftIdFromPath(newThreadPath); + // `toMatchObject` matches objects loosely (extras ignored) but compares + // arrays strictly, so wrap `options` in `arrayContaining` to keep the + // assertion focused on sticky `fastMode` carrying over without asserting + // on exactly which other options are preserved. expect(composerDraftFor(newDraftId)).toMatchObject({ modelSelectionByProvider: { codex: { provider: "codex", model: "gpt-5.3-codex", - options: { - fastMode: true, - }, + options: expect.arrayContaining([{ id: "fastMode", value: true }]), }, }, activeProvider: "codex", @@ -3830,14 +3829,10 @@ describe("ChatView timeline estimator parity (full app)", () => { it("hydrates the provider alongside a sticky claude model", async () => { useComposerDraftStore.setState({ stickyModelSelectionByProvider: { - claudeAgent: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - fastMode: true, - }, - }, + claudeAgent: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ]), }, stickyActiveProvider: "claudeAgent", }); @@ -3865,14 +3860,10 @@ describe("ChatView timeline estimator parity (full app)", () => { expect(composerDraftFor(newDraftId)).toMatchObject({ modelSelectionByProvider: { - claudeAgent: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { - effort: "max", - fastMode: true, - }, - }, + claudeAgent: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ]), }, activeProvider: "claudeAgent", }); @@ -3912,14 +3903,10 @@ describe("ChatView timeline estimator parity (full app)", () => { it("prefers draft state over sticky composer settings and defaults", async () => { useComposerDraftStore.setState({ stickyModelSelectionByProvider: { - codex: { - provider: "codex", - model: "gpt-5.3-codex", - options: { - reasoningEffort: "medium", - fastMode: true, - }, - }, + codex: createModelSelection("codex", "gpt-5.3-codex", [ + { id: "reasoningEffort", value: "medium" }, + { id: "fastMode", value: true }, + ]), }, stickyActiveProvider: "codex", }); @@ -3945,27 +3932,27 @@ describe("ChatView timeline estimator parity (full app)", () => { ); const draftId = draftIdFromPath(threadPath); + // See the note on the sibling sticky-codex test: arrays match strictly + // under `toMatchObject`, so use `arrayContaining` to keep the assertion + // scoped to the sticky trait (`fastMode`) that must carry over. expect(composerDraftFor(draftId)).toMatchObject({ modelSelectionByProvider: { codex: { provider: "codex", model: "gpt-5.3-codex", - options: { - fastMode: true, - }, + options: expect.arrayContaining([{ id: "fastMode", value: true }]), }, }, activeProvider: "codex", }); - useComposerDraftStore.getState().setModelSelection(draftId, { - provider: "codex", - model: "gpt-5.4", - options: { - reasoningEffort: "low", - fastMode: true, - }, - }); + useComposerDraftStore.getState().setModelSelection( + draftId, + createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "low" }, + { id: "fastMode", value: true }, + ]), + ); await newThreadButton.click(); @@ -3976,14 +3963,10 @@ describe("ChatView timeline estimator parity (full app)", () => { ); expect(composerDraftFor(draftId)).toMatchObject({ modelSelectionByProvider: { - codex: { - provider: "codex", - model: "gpt-5.4", - options: { - reasoningEffort: "low", - fastMode: true, - }, - }, + codex: createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "low" }, + { id: "fastMode", value: true }, + ]), }, activeProvider: "codex", }); @@ -5670,37 +5653,31 @@ describe("ChatView timeline estimator parity (full app)", () => { slug: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max", isCustom: false, - capabilities: { - supportsFastMode: true, - supportsThinkingToggle: false, - reasoningEffortLevels: [], - promptInjectedEffortLevels: [], - contextWindowOptions: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + { id: "fastMode", label: "Fast Mode", type: "boolean" as const }, + ], + }), }, { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex", isCustom: false, - capabilities: { - supportsFastMode: true, - supportsThinkingToggle: false, - reasoningEffortLevels: [], - promptInjectedEffortLevels: [], - contextWindowOptions: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + { id: "fastMode", label: "Fast Mode", type: "boolean" as const }, + ], + }), }, { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, - capabilities: { - supportsFastMode: true, - supportsThinkingToggle: false, - reasoningEffortLevels: [], - promptInjectedEffortLevels: [], - contextWindowOptions: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + { id: "fastMode", label: "Fast Mode", type: "boolean" as const }, + ], + }), }, ], }, diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 0c76059b6a8..3a71922706b 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -1,7 +1,6 @@ import { type ApprovalRequestId, DEFAULT_MODEL_BY_PROVIDER, - type ClaudeAgentEffort, type EnvironmentId, type MessageId, type ModelSelection, @@ -26,7 +25,11 @@ import { scopeProjectRef, scopeThreadRef, } from "@t3tools/client-runtime"; -import { applyClaudePromptEffortPrefix, createModelSelection } from "@t3tools/shared/model"; +import { + applyClaudePromptEffortPrefix, + createModelSelection, + resolvePromptInjectedEffort, +} from "@t3tools/shared/model"; import { projectScriptCwd, projectScriptRuntimeEnv } from "@t3tools/shared/projectScripts"; import { truncate } from "@t3tools/shared/String"; import { Debouncer } from "@tanstack/react-pacer"; @@ -306,10 +309,8 @@ function formatOutgoingPrompt(params: { text: string; }): string { const caps = getProviderModelCapabilities(params.models, params.model, params.provider); - if (params.effort && caps.promptInjectedEffortLevels.includes(params.effort)) { - return applyClaudePromptEffortPrefix(params.text, params.effort as ClaudeAgentEffort | null); - } - return params.text; + const promptEffort = resolvePromptInjectedEffort(caps, params.effort); + return applyClaudePromptEffortPrefix(params.text, promptEffort); } const SCRIPT_TERMINAL_COLS = 120; const SCRIPT_TERMINAL_ROWS = 30; diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 3d3b081af99..3b47b2067cd 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -71,11 +71,10 @@ import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; import { searchSlashCommandItems } from "./composerSlashCommandSearch"; import { - getComposerProviderControls, getComposerProviderState, renderProviderTraitsMenuContent, renderProviderTraitsPicker, -} from "./composerProviderRegistry"; +} from "./composerProviderState"; import { ContextWindowMeter } from "./ContextWindowMeter"; import { buildExpandedImagePreview, type ExpandedImagePreview } from "./ExpandedImagePreview"; import { basenameOfPath } from "../../vscode-icons"; @@ -96,7 +95,11 @@ import { XIcon, } from "lucide-react"; import { proposedPlanTitle } from "../../proposedPlan"; -import { resolveSelectableProvider, getProviderModels } from "../../providerModels"; +import { + getProviderInteractionModeToggle, + getProviderModels, + resolveSelectableProvider, +} from "../../providerModels"; import type { UnifiedSettings } from "@t3tools/contracts/settings"; import type { SessionPhase, Thread } from "../../types"; import type { PendingUserInputDraftAnswer } from "../../pendingUserInput"; @@ -595,7 +598,7 @@ export const ChatComposer = memo( model: selectedModel, models: selectedProviderModels, prompt, - modelOptions: composerModelOptions, + modelOptions: composerModelOptions?.[selectedProvider], }), [composerModelOptions, prompt, selectedModel, selectedProvider, selectedProviderModels], ); @@ -603,8 +606,13 @@ export const ChatComposer = memo( const selectedPromptEffort = composerProviderState.promptEffort; const selectedModelOptionsForDispatch = composerProviderState.modelOptionsForDispatch; const composerProviderControls = useMemo( - () => getComposerProviderControls(selectedProvider), - [selectedProvider], + () => ({ + showInteractionModeToggle: getProviderInteractionModeToggle( + providerStatuses, + selectedProvider, + ), + }), + [providerStatuses, selectedProvider], ); const selectedModelSelection = useMemo( () => createModelSelection(selectedProvider, selectedModel, selectedModelOptionsForDispatch), diff --git a/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx b/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx index 7619a635545..1808b94033c 100644 --- a/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx +++ b/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx @@ -10,6 +10,7 @@ import "../../index.css"; import { page } from "vitest/browser"; import { afterEach, describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-react"; +import { createModelCapabilities, createModelSelection } from "@t3tools/shared/model"; import { CompactComposerControlsMenu } from "./CompactComposerControlsMenu"; import { TraitsMenuContent } from "./TraitsPicker"; @@ -17,6 +18,34 @@ import { useComposerDraftStore } from "../../composerDraftStore"; const LOCAL_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); +function selectDescriptor( + id: string, + label: string, + options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, + promptInjectedValues?: ReadonlyArray, +) { + return { + id, + label, + type: "select" as const, + options: [...options], + ...(options.find((option) => option.isDefault)?.id + ? { currentValue: options.find((option) => option.isDefault)?.id } + : {}), + ...(promptInjectedValues && promptInjectedValues.length > 0 + ? { promptInjectedValues: [...promptInjectedValues] } + : {}), + }; +} + +function booleanDescriptor(id: string, label: string) { + return { + id, + label, + type: "boolean" as const, + }; +} + async function mountMenu(props?: { modelSelection?: ModelSelection; prompt?: string }) { const threadId = ThreadId.make("thread-compact-menu"); const threadRef = scopeThreadRef(LOCAL_ENVIRONMENT_ID, threadId); @@ -33,11 +62,7 @@ async function mountMenu(props?: { modelSelection?: ModelSelection; prompt?: str persistedAttachments: [], terminalContexts: [], modelSelectionByProvider: { - [provider]: { - provider, - model, - ...(props?.modelSelection?.options ? { options: props.modelSelection.options } : {}), - }, + [provider]: createModelSelection(provider, model, props?.modelSelection?.options), }, activeProvider: provider, runtimeMode: null, @@ -51,74 +76,58 @@ async function mountMenu(props?: { modelSelection?: ModelSelection; prompt?: str document.body.append(host); const onPromptChange = vi.fn(); const providerOptions = props?.modelSelection?.options; - const models = - provider === "claudeAgent" - ? [ - { - slug: "claude-opus-4-6", - name: "Claude Opus 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, - { - slug: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - ] - : [ - { - slug: "gpt-5.4", - name: "GPT-5.4", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "xhigh", label: "Extra High" }, - { value: "high", label: "High", isDefault: true }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, - ]; + const models = [ + { + slug: "claude-opus-4-6", + name: "Claude Opus 4.6", + isCustom: false, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor( + "effort", + "Reasoning", + [ + { id: "low", label: "Low" }, + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + { id: "max", label: "Max" }, + { id: "ultrathink", label: "Ultrathink" }, + ], + ["ultrathink"], + ), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), + }, + { + slug: "claude-haiku-4-5", + name: "Claude Haiku 4.5", + isCustom: false, + capabilities: createModelCapabilities({ + optionDescriptors: [booleanDescriptor("thinking", "Thinking")], + }), + }, + { + slug: "claude-sonnet-4-6", + name: "Claude Sonnet 4.6", + isCustom: false, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor( + "effort", + "Reasoning", + [ + { id: "low", label: "Low" }, + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + { id: "ultrathink", label: "Ultrathink" }, + ], + ["ultrathink"], + ), + ], + }), + }, + ]; const screen = await render( { it("shows fast mode controls for Opus", async () => { await using _ = await mountMenu({ - modelSelection: { provider: "claudeAgent", model: "claude-opus-4-6" }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6"), }); await page.getByLabelText("More composer controls").click(); @@ -177,14 +186,14 @@ describe("CompactComposerControlsMenu", () => { await vi.waitFor(() => { const text = document.body.textContent ?? ""; expect(text).toContain("Fast Mode"); - expect(text).toContain("off"); - expect(text).toContain("on"); + expect(text).toContain("On"); + expect(text).toContain("Off"); }); }); it("hides fast mode controls for non-Opus Claude models", async () => { await using _ = await mountMenu({ - modelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6"), }); await page.getByLabelText("More composer controls").click(); @@ -196,7 +205,7 @@ describe("CompactComposerControlsMenu", () => { it("shows only the provided effort options", async () => { await using _ = await mountMenu({ - modelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, + modelSelection: createModelSelection("claudeAgent", "claude-sonnet-4-6"), }); await page.getByLabelText("More composer controls").click(); @@ -213,11 +222,9 @@ describe("CompactComposerControlsMenu", () => { it("shows a Claude thinking on/off section for Haiku", async () => { await using _ = await mountMenu({ - modelSelection: { - provider: "claudeAgent", - model: "claude-haiku-4-5", - options: { thinking: true }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-haiku-4-5", [ + { id: "thinking", value: true }, + ]), }); await page.getByLabelText("More composer controls").click(); @@ -225,18 +232,16 @@ describe("CompactComposerControlsMenu", () => { await vi.waitFor(() => { const text = document.body.textContent ?? ""; expect(text).toContain("Thinking"); - expect(text).toContain("On (default)"); + expect(text).toContain("On"); expect(text).toContain("Off"); }); }); it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => { await using _ = await mountMenu({ - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { effort: "high" }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "high" }, + ]), prompt: "Ultrathink:\nInvestigate this", }); @@ -244,18 +249,16 @@ describe("CompactComposerControlsMenu", () => { await vi.waitFor(() => { const text = document.body.textContent ?? ""; - expect(text).toContain("Effort"); + expect(text).toContain("Reasoning"); expect(text).not.toContain("ultrathink"); }); }); it("warns when ultrathink appears in prompt body text", async () => { await using _ = await mountMenu({ - modelSelection: { - provider: "claudeAgent", - model: "claude-opus-4-6", - options: { effort: "high" }, - }, + modelSelection: createModelSelection("claudeAgent", "claude-opus-4-6", [ + { id: "effort", value: "high" }, + ]), prompt: "Ultrathink:\nplease ultrathink about this problem", }); @@ -264,7 +267,7 @@ describe("CompactComposerControlsMenu", () => { await vi.waitFor(() => { const text = document.body.textContent ?? ""; expect(text).toContain( - 'Your prompt contains "ultrathink" in the text. Remove it to change effort.', + 'Your prompt contains "ultrathink" in the text. Remove it to change this option.', ); }); }); diff --git a/apps/web/src/components/chat/ProviderModelPicker.browser.tsx b/apps/web/src/components/chat/ProviderModelPicker.browser.tsx index f4854dd9a62..a5f539ae5ac 100644 --- a/apps/web/src/components/chat/ProviderModelPicker.browser.tsx +++ b/apps/web/src/components/chat/ProviderModelPicker.browser.tsx @@ -1,5 +1,6 @@ import { type ProviderKind, type ServerProvider } from "@t3tools/contracts"; import { EnvironmentId } from "@t3tools/contracts"; +import { createModelCapabilities } from "@t3tools/shared/model"; import { page, userEvent } from "vitest/browser"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-react"; @@ -66,17 +67,34 @@ vi.mock("../../environments/runtime", () => { }; }); -function effort(value: string, isDefault = false) { +function selectDescriptor( + id: string, + label: string, + options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, +) { return { - value, - label: value, - ...(isDefault ? { isDefault: true } : {}), + id, + label, + type: "select" as const, + options: [...options], + ...(options.find((option) => option.isDefault)?.id + ? { currentValue: options.find((option) => option.isDefault)?.id } + : {}), + }; +} + +function booleanDescriptor(id: string, label: string) { + return { + id, + label, + type: "boolean" as const, }; } const TEST_PROVIDERS: ReadonlyArray = [ { provider: "codex", + displayName: "Codex", enabled: true, installed: true, version: "0.116.0", @@ -90,30 +108,37 @@ const TEST_PROVIDERS: ReadonlyArray = [ slug: "gpt-5-codex", name: "GPT-5 Codex", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, ], }, { provider: "claudeAgent", + displayName: "Claude", enabled: true, installed: true, version: "1.0.0", @@ -127,47 +152,48 @@ const TEST_PROVIDERS: ReadonlyArray = [ slug: "claude-opus-4-6", name: "Claude Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - effort("low"), - effort("medium", true), - effort("high"), - effort("max"), + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("effort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + { id: "max", label: "max" }, + ]), + booleanDescriptor("thinking", "Thinking"), ], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + }), }, { slug: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - effort("low"), - effort("medium", true), - effort("high"), - effort("max"), + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("effort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + { id: "max", label: "max" }, + ]), + booleanDescriptor("thinking", "Thinking"), ], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + }), }, { slug: "claude-haiku-4-5", name: "Claude Haiku 4.5", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("effort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("thinking", "Thinking"), + ], + }), }, ], }, @@ -176,6 +202,7 @@ const TEST_PROVIDERS: ReadonlyArray = [ function buildCodexProvider(models: ServerProvider["models"]): ServerProvider { return { provider: "codex", + displayName: "Codex", enabled: true, installed: true, version: "0.116.0", @@ -464,13 +491,15 @@ describe("ProviderModelPicker", () => { subProvider: "GitHub Copilot", shortName: "Opus 4.5", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + ], + }), }, ]), ]; @@ -658,13 +687,16 @@ describe("ProviderModelPicker", () => { slug: "gpt-5-codex", name: "GPT-5 Codex", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, ]), buildOpenCodeProvider([ @@ -673,13 +705,15 @@ describe("ProviderModelPicker", () => { name: "Claude Opus 4.7", subProvider: "GitHub Copilot", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + ], + }), }, ]), ]; @@ -712,13 +746,15 @@ describe("ProviderModelPicker", () => { name: "Claude Opus 4.7", subProvider: "GitHub Copilot", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + ], + }), }, ]), { @@ -728,18 +764,17 @@ describe("ProviderModelPicker", () => { slug: "claude-opus-4-6", name: "Claude Opus 4.6", isCustom: false, - capabilities: { - reasoningEffortLevels: [ - effort("low"), - effort("medium", true), - effort("high"), - effort("max"), + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("effort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + { id: "max", label: "max" }, + ]), + booleanDescriptor("thinking", "Thinking"), ], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + }), }, ], }, @@ -909,13 +944,16 @@ describe("ProviderModelPicker", () => { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, ]), TEST_PROVIDERS[1]!, @@ -926,25 +964,31 @@ describe("ProviderModelPicker", () => { slug: "gpt-5.3-codex", name: "GPT-5.3 Codex", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, { slug: "gpt-5.3-codex-spark", name: "GPT-5.3 Codex Spark", isCustom: false, - capabilities: { - reasoningEffortLevels: [effort("low"), effort("medium", true), effort("high")], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, + capabilities: createModelCapabilities({ + optionDescriptors: [ + selectDescriptor("reasoningEffort", "Reasoning", [ + { id: "low", label: "low" }, + { id: "medium", label: "medium", isDefault: true }, + { id: "high", label: "high" }, + ]), + booleanDescriptor("fastMode", "Fast Mode"), + ], + }), }, ]), TEST_PROVIDERS[1]!, diff --git a/apps/web/src/components/chat/ProviderStatusBanner.tsx b/apps/web/src/components/chat/ProviderStatusBanner.tsx index e709e75da37..5eaacafe052 100644 --- a/apps/web/src/components/chat/ProviderStatusBanner.tsx +++ b/apps/web/src/components/chat/ProviderStatusBanner.tsx @@ -1,7 +1,8 @@ -import { PROVIDER_DISPLAY_NAMES, type ServerProvider } from "@t3tools/contracts"; +import { type ServerProvider } from "@t3tools/contracts"; import { memo } from "react"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { CircleAlertIcon } from "lucide-react"; +import { formatProviderKindLabel } from "../../providerModels"; export const ProviderStatusBanner = memo(function ProviderStatusBanner({ status, @@ -12,7 +13,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({ return null; } - const providerLabel = PROVIDER_DISPLAY_NAMES[status.provider] ?? status.provider; + const providerLabel = status.displayName?.trim() || formatProviderKindLabel(status.provider); const defaultMessage = status.status === "error" ? `${providerLabel} provider is unavailable.` diff --git a/apps/web/src/components/chat/TraitsPicker.browser.tsx b/apps/web/src/components/chat/TraitsPicker.browser.tsx deleted file mode 100644 index dbfbf092878..00000000000 --- a/apps/web/src/components/chat/TraitsPicker.browser.tsx +++ /dev/null @@ -1,821 +0,0 @@ -import "../../index.css"; - -import { - type ModelSelection, - ClaudeModelOptions, - CodexModelOptions, - CursorModelOptions, - DEFAULT_MODEL_BY_PROVIDER, - DEFAULT_SERVER_SETTINGS, - OpenCodeModelOptions, - EnvironmentId, - type ServerProvider, - ThreadId, -} from "@t3tools/contracts"; -import { scopedThreadKey, scopeThreadRef } from "@t3tools/client-runtime"; -import { page } from "vitest/browser"; -import { useCallback } from "react"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { render } from "vitest-browser-react"; - -import { TraitsPicker } from "./TraitsPicker"; -import { - COMPOSER_DRAFT_STORAGE_KEY, - ComposerThreadDraftState, - useComposerDraftStore, - useComposerThreadDraft, - useEffectiveComposerModelState, -} from "../../composerDraftStore"; -import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; - -// ── Claude TraitsPicker tests ───────────────────────────────────────── - -const LOCAL_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); -const CLAUDE_THREAD_ID = ThreadId.make("thread-claude-traits"); -const CLAUDE_THREAD_REF = scopeThreadRef(LOCAL_ENVIRONMENT_ID, CLAUDE_THREAD_ID); -const CLAUDE_THREAD_KEY = scopedThreadKey(CLAUDE_THREAD_REF); -const CODEX_THREAD_ID = ThreadId.make("thread-codex-traits"); -const CODEX_THREAD_REF = scopeThreadRef(LOCAL_ENVIRONMENT_ID, CODEX_THREAD_ID); -const CODEX_THREAD_KEY = scopedThreadKey(CODEX_THREAD_REF); -const TEST_PROVIDERS: ReadonlyArray = [ - { - provider: "codex", - enabled: true, - installed: true, - version: "0.1.0", - status: "ready", - auth: { status: "authenticated" }, - checkedAt: "2026-01-01T00:00:00.000Z", - slashCommands: [], - skills: [], - models: [ - { - slug: "gpt-5.4", - name: "GPT-5.4", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "xhigh", label: "Extra High" }, - { value: "high", label: "High", isDefault: true }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, - ], - }, - { - provider: "opencode", - enabled: true, - installed: true, - version: "0.1.0", - status: "ready", - auth: { status: "authenticated" }, - checkedAt: "2026-01-01T00:00:00.000Z", - slashCommands: [], - skills: [], - models: [ - { - slug: "openai/gpt-5", - name: "GPT-5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - variantOptions: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium", isDefault: true }, - ], - agentOptions: [ - { value: "build", label: "Build", isDefault: true }, - { value: "plan", label: "Plan" }, - ], - }, - }, - ], - }, - { - provider: "claudeAgent", - enabled: true, - installed: true, - version: "0.1.0", - status: "ready", - auth: { status: "authenticated" }, - checkedAt: "2026-01-01T00:00:00.000Z", - slashCommands: [], - skills: [], - models: [ - { - slug: "claude-opus-4-6", - name: "Claude Opus 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, - ], - }, -]; -const findTestProvider = (provider: ServerProvider["provider"]) => { - const testProvider = TEST_PROVIDERS.find((candidate) => candidate.provider === provider); - if (!testProvider) { - throw new Error(`Missing test provider fixture: ${provider}`); - } - return testProvider; -}; - -function ClaudeTraitsPickerHarness(props: { - model: string; - fallbackModelSelection: ModelSelection | null; - triggerVariant?: "ghost" | "outline"; -}) { - const prompt = useComposerThreadDraft(CLAUDE_THREAD_REF).prompt; - const setPrompt = useComposerDraftStore((store) => store.setPrompt); - const { modelOptions, selectedModel } = useEffectiveComposerModelState({ - threadRef: CLAUDE_THREAD_REF, - providers: TEST_PROVIDERS, - selectedProvider: "claudeAgent", - threadModelSelection: props.fallbackModelSelection, - projectModelSelection: null, - settings: { - ...DEFAULT_SERVER_SETTINGS, - ...DEFAULT_CLIENT_SETTINGS, - }, - }); - const handlePromptChange = useCallback( - (nextPrompt: string) => { - setPrompt(CLAUDE_THREAD_REF, nextPrompt); - }, - [setPrompt], - ); - - return ( - - ); -} - -async function mountClaudePicker(props?: { - model?: string; - prompt?: string; - options?: ClaudeModelOptions; - fallbackModelOptions?: { - effort?: "low" | "medium" | "high" | "max" | "ultrathink"; - thinking?: boolean; - fastMode?: boolean; - } | null; - skipDraftModelOptions?: boolean; - triggerVariant?: "ghost" | "outline"; -}) { - const model = props?.model ?? "claude-opus-4-6"; - const claudeOptions = !props?.skipDraftModelOptions ? props?.options : undefined; - const draftsByThreadKey: Record = { - [CLAUDE_THREAD_KEY]: { - prompt: props?.prompt ?? "", - images: [], - nonPersistedImageIds: [], - persistedAttachments: [], - terminalContexts: [], - modelSelectionByProvider: props?.skipDraftModelOptions - ? {} - : { - claudeAgent: { - provider: "claudeAgent", - model, - ...(claudeOptions && Object.keys(claudeOptions).length > 0 - ? { options: claudeOptions } - : {}), - }, - }, - activeProvider: "claudeAgent", - runtimeMode: null, - interactionMode: null, - }, - }; - useComposerDraftStore.setState({ - draftsByThreadKey, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - }); - const host = document.createElement("div"); - document.body.append(host); - const fallbackModelSelection = - props?.fallbackModelOptions !== undefined - ? ({ - provider: "claudeAgent", - model, - ...(props.fallbackModelOptions ? { options: props.fallbackModelOptions } : {}), - } satisfies ModelSelection) - : null; - const screen = await render( - , - { container: host }, - ); - - const cleanup = async () => { - await screen.unmount(); - host.remove(); - }; - - return { - [Symbol.asyncDispose]: cleanup, - cleanup, - }; -} - -describe("TraitsPicker (Claude)", () => { - afterEach(() => { - document.body.innerHTML = ""; - useComposerDraftStore.setState({ - draftsByThreadKey: {}, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - stickyModelSelectionByProvider: {}, - }); - }); - - it("shows fast mode controls for Opus", async () => { - await using _ = await mountClaudePicker(); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Fast Mode"); - expect(text).toContain("off"); - expect(text).toContain("on"); - }); - }); - - it("hides fast mode controls for non-Opus models", async () => { - await using _ = await mountClaudePicker({ model: "claude-sonnet-4-6" }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - expect(document.body.textContent ?? "").not.toContain("Fast Mode"); - }); - }); - - it("shows only the provided effort options", async () => { - await using _ = await mountClaudePicker({ - model: "claude-sonnet-4-6", - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Low"); - expect(text).toContain("Medium"); - expect(text).toContain("High"); - expect(text).not.toContain("Max"); - expect(text).toContain("Ultrathink"); - }); - }); - - it("shows a th inking on/off dropdown for Haiku", async () => { - await using _ = await mountClaudePicker({ - model: "claude-haiku-4-5", - options: { thinking: true }, - }); - - await vi.waitFor(() => { - expect(document.body.textContent ?? "").toContain("Thinking On"); - }); - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Thinking"); - expect(text).toContain("On (default)"); - expect(text).toContain("Off"); - }); - }); - - it("shows prompt-controlled Ultrathink state with selectable effort controls", async () => { - await using _ = await mountClaudePicker({ - model: "claude-opus-4-6", - options: { effort: "high" }, - prompt: "Ultrathink:\nInvestigate this", - }); - - await vi.waitFor(() => { - expect(document.body.textContent ?? "").toContain("Ultrathink"); - expect(document.body.textContent ?? "").not.toContain("Ultrathink · Prompt"); - }); - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Effort"); - expect(text).not.toContain("ultrathink"); - }); - }); - - it("warns when ultrathink appears in prompt body text", async () => { - await using _ = await mountClaudePicker({ - model: "claude-opus-4-6", - options: { effort: "high" }, - prompt: "Ultrathink:\nplease ultrathink about this problem", - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain( - 'Your prompt contains "ultrathink" in the text. Remove it to change effort.', - ); - }); - }); - - it("persists sticky claude model options when traits change", async () => { - await using _ = await mountClaudePicker({ - model: "claude-opus-4-6", - options: { effort: "medium", fastMode: false }, - }); - - await page.getByRole("button").click(); - await page.getByRole("menuitemradio", { name: "Max" }).click(); - - expect( - useComposerDraftStore.getState().stickyModelSelectionByProvider.claudeAgent, - ).toMatchObject({ - provider: "claudeAgent", - options: { - effort: "max", - }, - }); - }); - - it("accepts outline trigger styling", async () => { - await using _ = await mountClaudePicker({ - triggerVariant: "outline", - }); - - const button = document.querySelector("button"); - if (!(button instanceof HTMLButtonElement)) { - throw new Error("Expected traits trigger button to be rendered."); - } - expect(button.className).toContain("border-input"); - expect(button.className).toContain("bg-popover"); - }); -}); - -// ── Codex TraitsPicker tests ────────────────────────────────────────── - -async function mountCodexPicker(props: { model?: string; options?: CodexModelOptions }) { - const model = props.model ?? DEFAULT_MODEL_BY_PROVIDER.codex; - const draftsByThreadKey: Record = { - [CODEX_THREAD_KEY]: { - prompt: "", - images: [], - nonPersistedImageIds: [], - persistedAttachments: [], - terminalContexts: [], - modelSelectionByProvider: { - codex: { - provider: "codex", - model, - ...(props.options ? { options: props.options } : {}), - }, - }, - activeProvider: "codex", - runtimeMode: null, - interactionMode: null, - }, - }; - - useComposerDraftStore.setState({ - draftsByThreadKey, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: { - "environment-local:project-codex-traits": CODEX_THREAD_KEY, - }, - }); - const host = document.createElement("div"); - document.body.append(host); - const screen = await render( - {}} - />, - { container: host }, - ); - - const cleanup = async () => { - await screen.unmount(); - host.remove(); - }; - - return { - [Symbol.asyncDispose]: cleanup, - cleanup, - }; -} - -async function mountCursorPicker(props: { model?: string; options?: CursorModelOptions }) { - const model = props.model ?? DEFAULT_MODEL_BY_PROVIDER.cursor; - const cursorThreadId = ThreadId.make("thread-cursor-traits"); - const cursorThreadRef = scopeThreadRef(LOCAL_ENVIRONMENT_ID, cursorThreadId); - const cursorThreadKey = scopedThreadKey(cursorThreadRef); - const host = document.createElement("div"); - document.body.append(host); - - useComposerDraftStore.setState({ - draftsByThreadKey: { - [cursorThreadKey]: { - prompt: "", - images: [], - nonPersistedImageIds: [], - persistedAttachments: [], - terminalContexts: [], - modelSelectionByProvider: { - cursor: { - provider: "cursor", - model, - ...(props.options ? { options: props.options } : {}), - }, - }, - activeProvider: "cursor", - runtimeMode: null, - interactionMode: null, - }, - }, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - }); - - const screen = await render( - {}} - />, - { container: host }, - ); - - const cleanup = async () => { - await screen.unmount(); - host.remove(); - }; - - return { - [Symbol.asyncDispose]: cleanup, - cleanup, - }; -} - -describe("TraitsPicker (Codex)", () => { - afterEach(() => { - document.body.innerHTML = ""; - localStorage.removeItem(COMPOSER_DRAFT_STORAGE_KEY); - useComposerDraftStore.setState({ - draftsByThreadKey: {}, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - stickyModelSelectionByProvider: {}, - }); - }); - - it("shows fast mode controls", async () => { - await using _ = await mountCodexPicker({ - options: { fastMode: false }, - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Fast Mode"); - expect(text).toContain("off"); - expect(text).toContain("on"); - }); - }); - - it("shows Fast in the trigger label when fast mode is active", async () => { - await using _ = await mountCodexPicker({ - options: { fastMode: true }, - }); - - await vi.waitFor(() => { - expect(document.body.textContent ?? "").toContain("High · Fast"); - }); - }); - - it("shows only the provided effort options", async () => { - await using _ = await mountCodexPicker({ - options: { fastMode: false }, - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Extra High"); - expect(text).toContain("High"); - expect(text).not.toContain("Low"); - expect(text).not.toContain("Medium"); - }); - }); - - it("persists sticky codex model options when traits change", async () => { - await using _ = await mountCodexPicker({ - options: { fastMode: false }, - }); - - await page.getByRole("button").click(); - await page.getByRole("menuitemradio", { name: "on" }).click(); - - expect(useComposerDraftStore.getState().stickyModelSelectionByProvider.codex).toMatchObject({ - provider: "codex", - options: { fastMode: true }, - }); - }); -}); - -// ── OpenCode TraitsPicker tests ─────────────────────────────────────── - -async function mountOpenCodePicker(props: { - model?: string; - options?: OpenCodeModelOptions; - models?: ServerProvider["models"]; -}) { - const threadId = ThreadId.make("thread-opencode-traits"); - const threadRef = scopeThreadRef(LOCAL_ENVIRONMENT_ID, threadId); - const threadKey = scopedThreadKey(threadRef); - const model = props.model ?? DEFAULT_MODEL_BY_PROVIDER.opencode; - const draftsByThreadKey: Record = { - [threadKey]: { - prompt: "", - images: [], - nonPersistedImageIds: [], - persistedAttachments: [], - terminalContexts: [], - modelSelectionByProvider: { - opencode: { - provider: "opencode", - model, - ...(props.options ? { options: props.options } : {}), - }, - }, - activeProvider: "opencode", - runtimeMode: null, - interactionMode: null, - }, - }; - - useComposerDraftStore.setState({ - draftsByThreadKey, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - }); - const host = document.createElement("div"); - document.body.append(host); - const screen = await render( - {}} - />, - { container: host }, - ); - - const cleanup = async () => { - await screen.unmount(); - host.remove(); - }; - - return { - [Symbol.asyncDispose]: cleanup, - cleanup, - }; -} - -describe("TraitsPicker (OpenCode)", () => { - afterEach(() => { - document.body.innerHTML = ""; - localStorage.removeItem(COMPOSER_DRAFT_STORAGE_KEY); - useComposerDraftStore.setState({ - draftsByThreadKey: {}, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - stickyModelSelectionByProvider: {}, - }); - }); - - it("shows the selected agent label with capitalization in the trigger", async () => { - await using _ = await mountOpenCodePicker({ - options: { - variant: "medium", - agent: "plan", - }, - }); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Medium · Plan"); - expect(text).not.toContain("Medium · plan"); - }); - }); - - it("does not show a leading separator when only agent options are available", async () => { - await using _ = await mountOpenCodePicker({ - model: "openai/gpt-5.4", - models: [ - { - slug: "openai/gpt-5.4", - name: "OpenAI · GPT-5.4", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - variantOptions: [], - agentOptions: [ - { value: "build", label: "Build", isDefault: true }, - { value: "plan", label: "Plan" }, - ], - }, - }, - ], - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Agent"); - expect(text).toContain("Build (default)"); - expect(text).toContain("Plan"); - expect(document.querySelectorAll('[data-slot="menu-separator"]')).toHaveLength(0); - }); - }); -}); - -describe("TraitsPicker (Cursor)", () => { - afterEach(() => { - document.body.innerHTML = ""; - localStorage.removeItem(COMPOSER_DRAFT_STORAGE_KEY); - useComposerDraftStore.setState({ - draftsByThreadKey: {}, - draftThreadsByThreadKey: {}, - logicalProjectDraftThreadKeyByLogicalProjectKey: {}, - stickyModelSelectionByProvider: {}, - }); - }); - - it("uses the selected fast mode menu label for the trigger in fast-only state", async () => { - await using _ = await mountCursorPicker({ - model: "composer-2", - options: { fastMode: false }, - }); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Normal"); - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Fast Mode"); - expect(text).toContain("off"); - expect(text).toContain("on"); - }); - }); - - it("shows Normal for Cursor Opus 4.6 when fast mode and context window are both at defaults", async () => { - await using _ = await mountCursorPicker({ - model: "claude-opus-4-6", - options: { fastMode: false }, - }); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Normal"); - }); - - await page.getByRole("button").click(); - - await vi.waitFor(() => { - const text = document.body.textContent ?? ""; - expect(text).toContain("Fast Mode"); - expect(text).toContain("Context Window"); - expect(text).toContain("200K (default)"); - expect(text).toContain("1M"); - }); - }); - - it("shows Normal · 1M for Cursor Opus 4.6 when fast mode is off and context window is overridden", async () => { - await using _ = await mountCursorPicker({ - model: "claude-opus-4-6", - options: { fastMode: false, contextWindow: "1m" }, - }); - - await vi.waitFor(() => { - expect(document.body.textContent ?? "").toContain("Normal · 1M"); - }); - }); -}); diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index d30fba832e6..1b54da76da5 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -1,21 +1,17 @@ import { - type ClaudeModelOptions, - type CodexModelOptions, - type CursorModelOptions, - type OpenCodeModelOptions, type ProviderKind, - type ProviderModelOptions, + type ProviderOptionDescriptor, + type ProviderOptionSelection, type ScopedThreadRef, type ServerProviderModel, } from "@t3tools/contracts"; import { applyClaudePromptEffortPrefix, + buildProviderOptionSelectionsFromDescriptors, + getProviderOptionCurrentLabel, + getProviderOptionCurrentValue, + getProviderOptionDescriptors, isClaudeUltrathinkPrompt, - trimOrNull, - getDefaultEffort, - getDefaultContextWindow, - hasContextWindowOption, - resolveEffort, } from "@t3tools/shared/model"; import { memo, useCallback, useState } from "react"; import type { VariantProps } from "class-variance-authority"; @@ -34,12 +30,7 @@ import { useComposerDraftStore, DraftId } from "../../composerDraftStore"; import { getProviderModelCapabilities } from "../../providerModels"; import { cn } from "~/lib/utils"; -type ProviderOptions = ProviderModelOptions[ProviderKind]; -type NamedOption = { - value: string; - label: string; - isDefault?: boolean | undefined; -}; +type ProviderOptions = ReadonlyArray; type TraitsPersistence = | { @@ -54,62 +45,34 @@ type TraitsPersistence = const ULTRATHINK_PROMPT_PREFIX = "Ultrathink:\n"; -function getRawEffort( - provider: ProviderKind, - modelOptions: ProviderOptions | null | undefined, -): string | null { - if (provider === "codex") { - return trimOrNull((modelOptions as CodexModelOptions | undefined)?.reasoningEffort); - } - if (provider === "cursor") { - return trimOrNull((modelOptions as CursorModelOptions | undefined)?.reasoning); - } - if (provider === "opencode") { - return trimOrNull((modelOptions as OpenCodeModelOptions | undefined)?.variant); - } - return trimOrNull((modelOptions as ClaudeModelOptions | undefined)?.effort); -} - -function getEffortKey(provider: ProviderKind): string { - if (provider === "codex") return "reasoningEffort"; - if (provider === "cursor") return "reasoning"; - if (provider === "opencode") return "variant"; - return "effort"; -} - -function getRawAgent(modelOptions: ProviderOptions | null | undefined): string | null { - return trimOrNull((modelOptions as OpenCodeModelOptions | undefined)?.agent); -} - -function resolveNamedOption( - options: ReadonlyArray, - raw: string | null, -): NamedOption | null { - if (raw) { - const matchingOption = options.find((option) => option.value === raw); - if (matchingOption) { - return matchingOption; - } - } - return options.find((option) => option.isDefault) ?? options[0] ?? null; +function replaceDescriptorCurrentValue( + descriptors: ReadonlyArray, + descriptorId: string, + currentValue: string | boolean | undefined, +): ReadonlyArray { + return descriptors.map((descriptor) => + descriptor.id !== descriptorId + ? descriptor + : descriptor.type === "boolean" + ? { + ...descriptor, + ...(typeof currentValue === "boolean" ? { currentValue } : {}), + } + : { + ...descriptor, + ...(typeof currentValue === "string" ? { currentValue } : {}), + }, + ); } -function getRawContextWindow( - provider: ProviderKind, - modelOptions: ProviderOptions | null | undefined, +function getDescriptorStringValue( + descriptor: Extract | null, ): string | null { - if (modelOptions && "contextWindow" in modelOptions) { - return trimOrNull(modelOptions.contextWindow); + if (!descriptor) { + return null; } - return null; -} - -function buildNextOptions( - provider: ProviderKind, - modelOptions: ProviderOptions | null | undefined, - patch: Record, -): ProviderOptions { - return { ...(modelOptions as Record | undefined), ...patch } as ProviderOptions; + const value = getProviderOptionCurrentValue(descriptor); + return typeof value === "string" ? value : null; } function getSelectedTraits( @@ -121,71 +84,68 @@ function getSelectedTraits( allowPromptInjectedEffort: boolean, ) { const caps = getProviderModelCapabilities(models, model, provider); - const effortLevels = - provider === "opencode" - ? (caps.variantOptions ?? []) - : allowPromptInjectedEffort - ? caps.reasoningEffortLevels - : caps.reasoningEffortLevels.filter( - (option) => !caps.promptInjectedEffortLevels.includes(option.value), - ); - - // Resolve effort from options (provider-specific key) - const rawEffort = getRawEffort(provider, modelOptions); - const effort = - provider === "opencode" - ? (resolveNamedOption(effortLevels, rawEffort)?.value ?? null) - : (resolveEffort(caps, rawEffort) ?? null); - - // Thinking toggle (only for models that support it) - const thinkingEnabled = caps.supportsThinkingToggle - ? modelOptions && "thinking" in modelOptions - ? modelOptions.thinking === true - : null - : null; - - // Fast mode - const fastModeEnabled = - caps.supportsFastMode && - (modelOptions as { fastMode?: boolean } | undefined)?.fastMode === true; - - // Context window - const contextWindowOptions = caps.contextWindowOptions; - const rawContextWindow = getRawContextWindow(provider, modelOptions); - const defaultContextWindow = getDefaultContextWindow(caps); - const contextWindow = - rawContextWindow && hasContextWindowOption(caps, rawContextWindow) - ? rawContextWindow - : defaultContextWindow; + const descriptors = getProviderOptionDescriptors({ + caps, + selections: modelOptions, + }); + const selectDescriptors = descriptors.filter( + (descriptor): descriptor is Extract => + descriptor.type === "select", + ); + const booleanDescriptors = descriptors.filter( + (descriptor): descriptor is Extract => + descriptor.type === "boolean", + ); + const primarySelectDescriptor = selectDescriptors[0] ?? null; + const contextWindowDescriptor = + selectDescriptors.find((descriptor) => descriptor.id === "contextWindow") ?? null; + const agentDescriptor = selectDescriptors.find((descriptor) => descriptor.id === "agent") ?? null; + const fastModeDescriptor = + booleanDescriptors.find((descriptor) => descriptor.id === "fastMode") ?? null; + const thinkingDescriptor = + booleanDescriptors.find((descriptor) => descriptor.id === "thinking") ?? null; // Prompt-controlled effort (e.g. ultrathink in prompt text) const ultrathinkPromptControlled = allowPromptInjectedEffort && - caps.promptInjectedEffortLevels.length > 0 && + (primarySelectDescriptor?.promptInjectedValues?.length ?? 0) > 0 && isClaudeUltrathinkPrompt(prompt); // Check if "ultrathink" appears in the body text (not just our prefix) const ultrathinkInBodyText = ultrathinkPromptControlled && isClaudeUltrathinkPrompt(prompt.replace(/^Ultrathink:\s*/i, "")); - - const agentOptions = caps.agentOptions ?? []; - const selectedAgentOption = - provider === "opencode" ? resolveNamedOption(agentOptions, getRawAgent(modelOptions)) : null; + const effort = + (ultrathinkPromptControlled + ? "ultrathink" + : getDescriptorStringValue(primarySelectDescriptor)) ?? null; + const thinkingEnabled = + typeof thinkingDescriptor?.currentValue === "boolean" ? thinkingDescriptor.currentValue : null; + const fastModeEnabled = + typeof fastModeDescriptor?.currentValue === "boolean" ? fastModeDescriptor.currentValue : false; + const contextWindow = getDescriptorStringValue(contextWindowDescriptor); + const selectedAgent = getDescriptorStringValue(agentDescriptor); + const selectedAgentLabel = agentDescriptor + ? getProviderOptionCurrentLabel(agentDescriptor) + : null; return { caps, + descriptors, + selectDescriptors, + booleanDescriptors, + primarySelectDescriptor, + contextWindowDescriptor, + agentDescriptor, + fastModeDescriptor, + thinkingDescriptor, effort, - effortLevels, thinkingEnabled, fastModeEnabled, - contextWindowOptions, contextWindow, - defaultContextWindow, ultrathinkPromptControlled, ultrathinkInBodyText, - agentOptions, - selectedAgent: selectedAgentOption?.value ?? null, - selectedAgentLabel: selectedAgentOption?.label ?? null, + selectedAgent, + selectedAgentLabel, }; } @@ -206,11 +166,11 @@ function getTraitsSectionVisibility(input: { input.allowPromptInjectedEffort ?? true, ); - const showEffort = selected.effort !== null; - const showThinking = selected.thinkingEnabled !== null; - const showFastMode = selected.caps.supportsFastMode; - const showContextWindow = selected.contextWindowOptions.length > 1; - const showAgent = selected.agentOptions.length > 0; + const showEffort = selected.primarySelectDescriptor !== null; + const showThinking = selected.thinkingDescriptor !== null; + const showFastMode = selected.fastModeDescriptor !== null; + const showContextWindow = selected.contextWindowDescriptor !== null; + const showAgent = selected.agentDescriptor !== null; return { ...selected, @@ -275,22 +235,12 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ [model, persistence, provider, setProviderModelOptions], ); const { - caps, - effort, - effortLevels, - thinkingEnabled, - fastModeEnabled, - contextWindowOptions, - contextWindow, - defaultContextWindow, + descriptors, + selectDescriptors, + booleanDescriptors, + primarySelectDescriptor, ultrathinkPromptControlled, - showEffort, - showThinking, - showFastMode, - showContextWindow, ultrathinkInBodyText, - agentOptions, - selectedAgent, hasAnyControls, } = getTraitsSectionVisibility({ provider, @@ -300,53 +250,30 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ modelOptions, allowPromptInjectedEffort, }); - const defaultEffort = getDefaultEffort(caps); - const showsEffortSection = showEffort; - const showsThinkingSection = !showEffort && showThinking; - const showsFastModeSection = showFastMode; - const showsContextWindowSection = showContextWindow; - const hasSectionsBeforeAgent = - showsEffortSection || showsThinkingSection || showsFastModeSection || showsContextWindowSection; + const updateDescriptors = (nextDescriptors: ReadonlyArray) => { + updateModelOptions(buildProviderOptionSelectionsFromDescriptors(nextDescriptors)); + }; - const handleEffortChange = useCallback( - (value: string) => { - if (!value) return; - const nextOption = effortLevels.find((option) => option.value === value); - if (!nextOption) return; - if (provider === "opencode") { - updateModelOptions(buildNextOptions(provider, modelOptions, { variant: nextOption.value })); - return; - } - if (caps.promptInjectedEffortLevels.includes(nextOption.value)) { - const nextPrompt = - prompt.trim().length === 0 - ? ULTRATHINK_PROMPT_PREFIX - : applyClaudePromptEffortPrefix(prompt, "ultrathink"); - onPromptChange(nextPrompt); - return; - } - if (ultrathinkInBodyText) return; - if (ultrathinkPromptControlled) { - const stripped = prompt.replace(/^Ultrathink:\s*/i, ""); - onPromptChange(stripped); - } - const effortKey = getEffortKey(provider); - updateModelOptions( - buildNextOptions(provider, modelOptions, { [effortKey]: nextOption.value }), - ); - }, - [ - ultrathinkPromptControlled, - ultrathinkInBodyText, - modelOptions, - onPromptChange, - updateModelOptions, - effortLevels, - prompt, - caps.promptInjectedEffortLevels, - provider, - ], - ); + const handleSelectChange = ( + descriptor: Extract, + value: string, + ) => { + if (!value) return; + if (descriptor.promptInjectedValues?.includes(value)) { + const nextPrompt = + prompt.trim().length === 0 + ? ULTRATHINK_PROMPT_PREFIX + : applyClaudePromptEffortPrefix(prompt, "ultrathink"); + onPromptChange(nextPrompt); + return; + } + if (ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id) return; + if (ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id) { + const stripped = prompt.replace(/^Ultrathink:\s*/i, ""); + onPromptChange(stripped); + } + updateDescriptors(replaceDescriptorCurrentValue(descriptors, descriptor.id, value)); + }; if (!hasAnyControls) { return null; @@ -354,121 +281,62 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ return ( <> - {showsEffortSection ? ( - <> + {selectDescriptors.map((descriptor, index) => ( +
+ {index > 0 ? : null}
- {provider === "opencode" ? "Variant" : "Effort"} + {descriptor.label}
- {ultrathinkInBodyText ? ( + {ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? (
- Your prompt contains "ultrathink" in the text. Remove it to change effort. + Your prompt contains "ultrathink" in the text. Remove it to change this + option.
) : null} handleSelectChange(descriptor, value)} > - {effortLevels.map((option) => ( + {descriptor.options.map((option) => ( {option.label} - {(provider === "opencode" ? option.isDefault : option.value === defaultEffort) - ? " (default)" - : ""} + {option.isDefault ? " (default)" : ""} ))}
- - ) : showsThinkingSection ? ( - -
Thinking
- { - updateModelOptions( - buildNextOptions(provider, modelOptions, { thinking: value === "on" }), - ); - }} - > - On (default) - Off - -
- ) : null} - {showsFastModeSection ? ( - <> - {showsEffortSection || showsThinkingSection ? : null} - -
Fast Mode
- { - updateModelOptions( - buildNextOptions(provider, modelOptions, { fastMode: value === "on" }), - ); - }} - > - off - on - -
- - ) : null} - {showsContextWindowSection ? ( - <> - {showsEffortSection || showsThinkingSection || showsFastModeSection ? ( - - ) : null} +
+ ))} + {booleanDescriptors.map((descriptor, index) => ( +
+ {index > 0 || selectDescriptors.length > 0 ? : null}
- Context Window + {descriptor.label}
{ - updateModelOptions( - buildNextOptions(provider, modelOptions, { - contextWindow: value, - }), + updateDescriptors( + replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"), ); }} > - {contextWindowOptions.map((option) => ( - - {option.label} - {option.value === defaultContextWindow ? " (default)" : ""} - - ))} - -
- - ) : null} - {agentOptions.length > 0 ? ( - <> - {hasSectionsBeforeAgent ? : null} - -
Agent
- { - updateModelOptions(buildNextOptions(provider, modelOptions, { agent: value })); - }} - > - {agentOptions.map((option) => ( - - {option.label} - {option.isDefault ? " (default)" : ""} - - ))} + On + Off
- - ) : null} +
+ ))} ); }); @@ -486,52 +354,15 @@ export const TraitsPicker = memo(function TraitsPicker({ ...persistence }: TraitsMenuContentProps & TraitsPersistence) { const [isMenuOpen, setIsMenuOpen] = useState(false); - const { - caps, - effort, - effortLevels, - thinkingEnabled, - fastModeEnabled, - contextWindowOptions, - contextWindow, - defaultContextWindow, - ultrathinkPromptControlled, - showEffort, - showThinking, - showContextWindow, - } = getTraitsSectionVisibility({ - provider, - models, - model, - prompt, - modelOptions, - allowPromptInjectedEffort, - }); - const { selectedAgentLabel } = getSelectedTraits( - provider, - models, - model, - prompt, - modelOptions, - allowPromptInjectedEffort, - ); - - const effortLabel = effort - ? (effortLevels.find((l) => l.value === effort)?.label ?? effort) - : null; - const primaryTraitLabel = ultrathinkPromptControlled - ? "Ultrathink" - : effortLabel - ? effortLabel - : thinkingEnabled === null - ? null - : `Thinking ${thinkingEnabled ? "On" : "Off"}`; - const contextWindowLabel = - showContextWindow && contextWindow !== defaultContextWindow - ? (contextWindowOptions.find((o) => o.value === contextWindow)?.label ?? null) - : null; - const fastOnlyControl = - caps.supportsFastMode && !showEffort && !showThinking && !showContextWindow; + const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } = + getTraitsSectionVisibility({ + provider, + models, + model, + prompt, + modelOptions, + allowPromptInjectedEffort, + }); if ( !shouldRenderTraitsControls({ provider, @@ -545,27 +376,22 @@ export const TraitsPicker = memo(function TraitsPicker({ return null; } - const selectedTriggerTraits = [ - primaryTraitLabel, - ...(caps.supportsFastMode && - (fastModeEnabled || (primaryTraitLabel === null && contextWindowLabel !== null)) - ? [fastModeEnabled ? "Fast" : "Normal"] - : []), - ...(contextWindowLabel ? [contextWindowLabel] : []), - ...(selectedAgentLabel ? [selectedAgentLabel] : []), - ].filter(Boolean); - const triggerLabel = fastOnlyControl - ? fastModeEnabled - ? "Fast" - : "Normal" - : selectedTriggerTraits.length > 0 - ? selectedTriggerTraits.join(" · ") - : caps.supportsFastMode - ? "Normal" - : defaultContextWindow - ? (contextWindowOptions.find((option) => option.value === defaultContextWindow)?.label ?? - defaultContextWindow) - : (selectedAgentLabel ?? ""); + const triggerLabel = + descriptors + .map((descriptor) => { + if (ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id) { + return "Ultrathink"; + } + if (descriptor.type === "boolean") { + if (descriptor.id === "fastMode") { + return descriptor.currentValue === true ? "Fast" : "Normal"; + } + return `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}`; + } + return getProviderOptionCurrentLabel(descriptor); + }) + .filter((label): label is string => typeof label === "string" && label.length > 0) + .join(" · ") || ""; const isCodexStyle = provider === "codex"; diff --git a/apps/web/src/components/chat/composerProviderRegistry.test.tsx b/apps/web/src/components/chat/composerProviderRegistry.test.tsx deleted file mode 100644 index c4dd2cbb6ee..00000000000 --- a/apps/web/src/components/chat/composerProviderRegistry.test.tsx +++ /dev/null @@ -1,516 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { ServerProviderModel } from "@t3tools/contracts"; -import { - getComposerProviderControls, - getComposerProviderState, - renderProviderTraitsMenuContent, - renderProviderTraitsPicker, -} from "./composerProviderRegistry"; - -const CODEX_MODELS: ReadonlyArray = [ - { - slug: "gpt-5.4", - name: "GPT-5.4", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "xhigh", label: "Extra High" }, - { value: "high", label: "High", isDefault: true }, - { value: "medium", label: "Medium" }, - { value: "low", label: "Low" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, -]; - -const CLAUDE_MODELS: ReadonlyArray = [ - { - slug: "claude-opus-4-6", - name: "Claude Opus 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-sonnet-4-6", - name: "Claude Sonnet 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, -]; - -const CLAUDE_MODELS_WITH_CONTEXT_WINDOW: ReadonlyArray = [ - { - slug: "claude-opus-4-6", - name: "Claude Opus 4.6", - isCustom: false, - capabilities: { - reasoningEffortLevels: [ - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "max", label: "Max" }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, - ], - promptInjectedEffortLevels: ["ultrathink"], - }, - }, - { - slug: "claude-haiku-4-5", - name: "Claude Haiku 4.5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: true, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }, - }, -]; - -const OPENCODE_MODELS: ReadonlyArray = [ - { - slug: "openai/gpt-5", - name: "GPT-5", - isCustom: false, - capabilities: { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - variantOptions: [ - { value: "low", label: "Low" }, - { value: "medium", label: "Medium", isDefault: true }, - ], - agentOptions: [ - { value: "build", label: "Build", isDefault: true }, - { value: "plan", label: "Plan" }, - ], - }, - }, -]; - -describe("getComposerProviderState", () => { - it("returns codex defaults when no codex draft options exist", () => { - const state = getComposerProviderState({ - provider: "codex", - model: "gpt-5.4", - models: CODEX_MODELS, - prompt: "", - modelOptions: undefined, - }); - - expect(state).toEqual({ - provider: "codex", - promptEffort: "high", - modelOptionsForDispatch: { - reasoningEffort: "high", - }, - }); - }); - - it("normalizes codex dispatch options while preserving the selected effort", () => { - const state = getComposerProviderState({ - provider: "codex", - model: "gpt-5.4", - models: CODEX_MODELS, - prompt: "", - modelOptions: { - codex: { - reasoningEffort: "low", - fastMode: true, - }, - }, - }); - - expect(state).toEqual({ - provider: "codex", - promptEffort: "low", - modelOptionsForDispatch: { - reasoningEffort: "low", - fastMode: true, - }, - }); - }); - - it("preserves codex fast mode when it is the only active option", () => { - const state = getComposerProviderState({ - provider: "codex", - model: "gpt-5.4", - models: CODEX_MODELS, - prompt: "", - modelOptions: { - codex: { - fastMode: true, - }, - }, - }); - - expect(state).toEqual({ - provider: "codex", - promptEffort: "high", - modelOptionsForDispatch: { - reasoningEffort: "high", - fastMode: true, - }, - }); - }); - - it("preserves codex default effort explicitly in dispatch options", () => { - const state = getComposerProviderState({ - provider: "codex", - model: "gpt-5.4", - models: CODEX_MODELS, - prompt: "", - modelOptions: { - codex: { - reasoningEffort: "high", - fastMode: false, - }, - }, - }); - - expect(state).toEqual({ - provider: "codex", - promptEffort: "high", - modelOptionsForDispatch: { - reasoningEffort: "high", - fastMode: false, - }, - }); - }); - - it("returns Claude defaults for effort-capable models", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-sonnet-4-6", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: undefined, - }); - - expect(state).toEqual({ - provider: "claudeAgent", - promptEffort: "high", - modelOptionsForDispatch: { - effort: "high", - }, - }); - }); - - it("tracks Claude ultrathink from the prompt without changing dispatch effort", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-sonnet-4-6", - models: CLAUDE_MODELS, - prompt: "Ultrathink:\nInvestigate this failure", - modelOptions: { - claudeAgent: { - effort: "medium", - }, - }, - }); - - expect(state).toEqual({ - provider: "claudeAgent", - promptEffort: "medium", - modelOptionsForDispatch: { - effort: "medium", - }, - composerFrameClassName: "ultrathink-frame", - composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]", - modelPickerIconClassName: "ultrathink-chroma", - }); - }); - - it("drops unsupported Claude effort options for models without effort controls", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-haiku-4-5", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - effort: "max", - thinking: false, - }, - }, - }); - - expect(state).toEqual({ - provider: "claudeAgent", - promptEffort: null, - modelOptionsForDispatch: { - thinking: false, - }, - }); - }); - - it("preserves Claude fast mode when it is the only active option", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-opus-4-6", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - fastMode: true, - }, - }, - }); - - expect(state).toEqual({ - provider: "claudeAgent", - promptEffort: "high", - modelOptionsForDispatch: { - effort: "high", - fastMode: true, - }, - }); - }); - - it("preserves Claude default effort explicitly in dispatch options", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-opus-4-6", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - effort: "high", - fastMode: false, - }, - }, - }); - - expect(state).toEqual({ - provider: "claudeAgent", - promptEffort: "high", - modelOptionsForDispatch: { - effort: "high", - fastMode: false, - }, - }); - }); - - it("preserves explicit fastMode: false so deepMerge can overwrite a prior true", () => { - // Regression: normalizeClaudeModelOptionsWithCapabilities used to strip - // fastMode: false, which meant deepMerge could never clear a previous true. - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-opus-4-6", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - effort: "high", - fastMode: false, - }, - }, - }); - - expect(state.modelOptionsForDispatch).toHaveProperty("fastMode", false); - }); - - it("preserves explicit thinking: true so deepMerge can overwrite a prior false", () => { - // Regression: thinking: true (the default) used to be stripped, which - // meant deepMerge could never clear a previous thinking: false. - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-haiku-4-5", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - thinking: true, - }, - }, - }); - - expect(state.modelOptionsForDispatch).toHaveProperty("thinking", true); - }); - - it("preserves Claude default context window explicitly in dispatch options", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-opus-4-6", - models: CLAUDE_MODELS_WITH_CONTEXT_WINDOW, - prompt: "", - modelOptions: { - claudeAgent: { - effort: "high", - contextWindow: "200k", - }, - }, - }); - - expect(state.modelOptionsForDispatch).toMatchObject({ - effort: "high", - contextWindow: "200k", - }); - }); - - it("preserves explicit contextWindow default so deepMerge can overwrite a prior 1m", () => { - // Regression: the default contextWindow must survive normalization so - // deepMerge can clear an older non-default 1m selection. - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-opus-4-6", - models: CLAUDE_MODELS_WITH_CONTEXT_WINDOW, - prompt: "", - modelOptions: { - claudeAgent: { - contextWindow: "200k", - }, - }, - }); - - expect(state.modelOptionsForDispatch).toHaveProperty("contextWindow", "200k"); - }); - - it("omits contextWindow when the model does not support it", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-haiku-4-5", - models: CLAUDE_MODELS_WITH_CONTEXT_WINDOW, - prompt: "", - modelOptions: { - claudeAgent: { - contextWindow: "1m", - }, - }, - }); - - expect(state.modelOptionsForDispatch).toBeUndefined(); - }); - - it("omits fastMode when the model does not support it", () => { - const state = getComposerProviderState({ - provider: "claudeAgent", - model: "claude-sonnet-4-6", - models: CLAUDE_MODELS, - prompt: "", - modelOptions: { - claudeAgent: { - effort: "high", - fastMode: true, - }, - }, - }); - - expect(state.modelOptionsForDispatch).not.toHaveProperty("fastMode"); - }); - - it("preserves OpenCode variant and agent options for dispatch", () => { - const state = getComposerProviderState({ - provider: "opencode", - model: "openai/gpt-5", - models: OPENCODE_MODELS, - prompt: "", - modelOptions: { - opencode: { - variant: "medium", - agent: "plan", - }, - }, - }); - - expect(state).toEqual({ - provider: "opencode", - promptEffort: "medium", - modelOptionsForDispatch: { - variant: "medium", - agent: "plan", - }, - }); - }); -}); - -describe("getComposerProviderControls", () => { - it("hides the interaction mode toggle for OpenCode", () => { - expect(getComposerProviderControls("opencode")).toEqual({ - showInteractionModeToggle: false, - }); - }); - - it("keeps the interaction mode toggle for Codex and Claude", () => { - expect(getComposerProviderControls("codex")).toEqual({ - showInteractionModeToggle: true, - }); - expect(getComposerProviderControls("claudeAgent")).toEqual({ - showInteractionModeToggle: true, - }); - }); -}); - -describe("provider traits render guards", () => { - it("returns null for codex traits picker when no thread target is provided", () => { - const content = renderProviderTraitsPicker({ - provider: "codex", - model: "gpt-5.4", - models: CODEX_MODELS, - modelOptions: undefined, - prompt: "", - onPromptChange: () => {}, - }); - - expect(content).toBeNull(); - }); - - it("returns null for claude traits menu content when no thread target is provided", () => { - const content = renderProviderTraitsMenuContent({ - provider: "claudeAgent", - model: "claude-sonnet-4-6", - models: CLAUDE_MODELS, - modelOptions: undefined, - prompt: "", - onPromptChange: () => {}, - }); - - expect(content).toBeNull(); - }); -}); diff --git a/apps/web/src/components/chat/composerProviderRegistry.tsx b/apps/web/src/components/chat/composerProviderRegistry.tsx deleted file mode 100644 index af03c99a5e7..00000000000 --- a/apps/web/src/components/chat/composerProviderRegistry.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { - type ProviderKind, - type ProviderModelOptions, - type ScopedThreadRef, - type ServerProviderModel, -} from "@t3tools/contracts"; -import { - isClaudeUltrathinkPrompt, - normalizeProviderModelOptionsWithCapabilities, - resolveEffort, - trimOrNull, -} from "@t3tools/shared/model"; -import type { ReactNode } from "react"; - -import type { DraftId } from "../../composerDraftStore"; -import { getProviderModelCapabilities } from "../../providerModels"; -import { shouldRenderTraitsControls, TraitsMenuContent, TraitsPicker } from "./TraitsPicker"; - -export type ComposerProviderStateInput = { - provider: ProviderKind; - model: string; - models: ReadonlyArray; - prompt: string; - modelOptions: ProviderModelOptions | null | undefined; -}; - -export type ComposerProviderState = { - provider: ProviderKind; - promptEffort: string | null; - modelOptionsForDispatch: ProviderModelOptions[ProviderKind] | undefined; - composerFrameClassName?: string; - composerSurfaceClassName?: string; - modelPickerIconClassName?: string; -}; - -type TraitsRenderInput = { - threadRef?: ScopedThreadRef; - draftId?: DraftId; - model: string; - models: ReadonlyArray; - modelOptions: ProviderModelOptions[ProviderKind] | undefined; - prompt: string; - onPromptChange: (prompt: string) => void; -}; - -export type ComposerProviderControls = { - showInteractionModeToggle: boolean; -}; - -type ProviderRegistryEntry = { - controls: ComposerProviderControls; - getState: (input: ComposerProviderStateInput) => ComposerProviderState; - renderTraitsMenuContent: (input: TraitsRenderInput) => ReactNode; - renderTraitsPicker: (input: TraitsRenderInput) => ReactNode; -}; - -function hasComposerTraitsTarget(input: { - threadRef: ScopedThreadRef | undefined; - draftId: DraftId | undefined; -}): boolean { - return input.threadRef !== undefined || input.draftId !== undefined; -} - -function renderTraitsControl( - Component: typeof TraitsMenuContent | typeof TraitsPicker, - provider: ProviderKind, - input: TraitsRenderInput, -): ReactNode { - const { threadRef, draftId, model, models, modelOptions, prompt, onPromptChange } = input; - if ( - !hasComposerTraitsTarget({ threadRef, draftId }) || - !shouldRenderTraitsControls({ - provider, - models, - model, - modelOptions, - prompt, - }) - ) { - return null; - } - - return ( - - ); -} - -function getProviderStateFromCapabilities( - input: ComposerProviderStateInput, -): ComposerProviderState { - const { provider, model, models, prompt, modelOptions } = input; - const caps = getProviderModelCapabilities(models, model, provider); - const providerOptions = modelOptions?.[provider]; - const rawEffort = providerOptions - ? "effort" in providerOptions - ? providerOptions.effort - : "reasoningEffort" in providerOptions - ? providerOptions.reasoningEffort - : "reasoning" in providerOptions - ? providerOptions.reasoning - : "variant" in providerOptions - ? providerOptions.variant - : null - : null; - const normalizedOptions = normalizeProviderModelOptionsWithCapabilities( - provider, - caps, - providerOptions, - ); - const promptEffort = - provider === "opencode" - ? (trimOrNull( - normalizedOptions && "variant" in normalizedOptions ? normalizedOptions.variant : null, - ) ?? null) - : (resolveEffort(caps, rawEffort) ?? null); - const ultrathinkActive = - caps.promptInjectedEffortLevels.length > 0 && isClaudeUltrathinkPrompt(prompt); - - return { - provider, - promptEffort, - modelOptionsForDispatch: normalizedOptions, - ...(ultrathinkActive ? { composerFrameClassName: "ultrathink-frame" } : {}), - ...(ultrathinkActive - ? { composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]" } - : {}), - ...(ultrathinkActive ? { modelPickerIconClassName: "ultrathink-chroma" } : {}), - }; -} - -const DEFAULT_PROVIDER_CONTROLS: ComposerProviderControls = { - showInteractionModeToggle: true, -}; - -function createProviderRegistryEntry( - provider: ProviderKind, - controls?: Partial, -): ProviderRegistryEntry { - return { - controls: { - ...DEFAULT_PROVIDER_CONTROLS, - ...controls, - }, - getState: (input) => getProviderStateFromCapabilities(input), - renderTraitsMenuContent: (input) => renderTraitsControl(TraitsMenuContent, provider, input), - renderTraitsPicker: (input) => renderTraitsControl(TraitsPicker, provider, input), - }; -} - -const composerProviderRegistry: Record = { - codex: createProviderRegistryEntry("codex"), - claudeAgent: createProviderRegistryEntry("claudeAgent"), - cursor: createProviderRegistryEntry("cursor"), - opencode: createProviderRegistryEntry("opencode", { - showInteractionModeToggle: false, - }), -}; - -export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState { - return composerProviderRegistry[input.provider].getState(input); -} - -export function getComposerProviderControls(provider: ProviderKind): ComposerProviderControls { - return composerProviderRegistry[provider].controls; -} - -export function renderProviderTraitsMenuContent(input: { - provider: ProviderKind; - threadRef?: ScopedThreadRef; - draftId?: DraftId; - model: string; - models: ReadonlyArray; - modelOptions: ProviderModelOptions[ProviderKind] | undefined; - prompt: string; - onPromptChange: (prompt: string) => void; -}): ReactNode { - return composerProviderRegistry[input.provider].renderTraitsMenuContent({ - ...(input.threadRef ? { threadRef: input.threadRef } : {}), - ...(input.draftId ? { draftId: input.draftId } : {}), - model: input.model, - models: input.models, - modelOptions: input.modelOptions, - prompt: input.prompt, - onPromptChange: input.onPromptChange, - }); -} - -export function renderProviderTraitsPicker(input: { - provider: ProviderKind; - threadRef?: ScopedThreadRef; - draftId?: DraftId; - model: string; - models: ReadonlyArray; - modelOptions: ProviderModelOptions[ProviderKind] | undefined; - prompt: string; - onPromptChange: (prompt: string) => void; -}): ReactNode { - return composerProviderRegistry[input.provider].renderTraitsPicker({ - ...(input.threadRef ? { threadRef: input.threadRef } : {}), - ...(input.draftId ? { draftId: input.draftId } : {}), - model: input.model, - models: input.models, - modelOptions: input.modelOptions, - prompt: input.prompt, - onPromptChange: input.onPromptChange, - }); -} diff --git a/apps/web/src/components/chat/composerProviderState.test.tsx b/apps/web/src/components/chat/composerProviderState.test.tsx new file mode 100644 index 00000000000..cc49cbf5dfc --- /dev/null +++ b/apps/web/src/components/chat/composerProviderState.test.tsx @@ -0,0 +1,242 @@ +import { describe, expect, it } from "vitest"; +import type { + ProviderKind, + ProviderOptionDescriptor, + ProviderOptionSelection, + ServerProviderModel, +} from "@t3tools/contracts"; +import { + getComposerProviderState, + renderProviderTraitsMenuContent, + renderProviderTraitsPicker, +} from "./composerProviderState"; + +// Everything in composerProviderState is now data-driven by the model's +// optionDescriptors, so these tests use a single synthetic provider/model and +// vary only the descriptor shape per scenario. + +const PROVIDER: ProviderKind = "codex"; +const MODEL = "test-model"; + +function selectDescriptor( + id: string, + options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, + promptInjectedValues?: ReadonlyArray, +): Extract { + const defaultId = options.find((option) => option.isDefault)?.id; + return { + id, + label: id, + type: "select", + options: [...options], + ...(defaultId ? { currentValue: defaultId } : {}), + ...(promptInjectedValues && promptInjectedValues.length > 0 + ? { promptInjectedValues: [...promptInjectedValues] } + : {}), + }; +} + +function booleanDescriptor(id: string): Extract { + return { id, label: id, type: "boolean" }; +} + +function modelWith( + descriptors: ReadonlyArray, +): ReadonlyArray { + return [ + { slug: MODEL, name: MODEL, isCustom: false, capabilities: { optionDescriptors: descriptors } }, + ]; +} + +function selections( + ...entries: Array<[string, string | boolean]> +): ReadonlyArray { + return entries.map(([id, value]) => ({ id, value })); +} + +const ULTRATHINK_FRAME_CLASSES = { + composerFrameClassName: "ultrathink-frame", + composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]", + modelPickerIconClassName: "ultrathink-chroma", +} as const; + +describe("getComposerProviderState", () => { + it("returns descriptor defaults when no selections are provided", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor("effort", [ + { id: "low", label: "Low" }, + { id: "high", label: "High", isDefault: true }, + ]), + ]), + prompt: "", + modelOptions: undefined, + }); + + expect(state).toEqual({ + provider: PROVIDER, + promptEffort: "high", + modelOptionsForDispatch: selections(["effort", "high"]), + }); + }); + + it("lets selections override defaults and propagates them through dispatch", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor("effort", [ + { id: "low", label: "Low" }, + { id: "high", label: "High", isDefault: true }, + ]), + booleanDescriptor("fastMode"), + ]), + prompt: "", + modelOptions: selections(["effort", "low"], ["fastMode", true]), + }); + + expect(state).toEqual({ + provider: PROVIDER, + promptEffort: "low", + modelOptionsForDispatch: selections(["effort", "low"], ["fastMode", true]), + }); + }); + + it("preserves selections that match defaults so deepMerge can overwrite prior state", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), + booleanDescriptor("fastMode"), + ]), + prompt: "", + modelOptions: selections(["effort", "high"], ["fastMode", false]), + }); + + expect(state.modelOptionsForDispatch).toEqual( + selections(["effort", "high"], ["fastMode", false]), + ); + }); + + it("drops selections for descriptors the model does not declare", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([booleanDescriptor("thinking")]), + prompt: "", + modelOptions: selections(["effort", "max"], ["thinking", false]), + }); + + expect(state).toEqual({ + provider: PROVIDER, + promptEffort: null, + modelOptionsForDispatch: selections(["thinking", false]), + }); + }); + + it("derives promptEffort from the first select descriptor and preserves all others for dispatch", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), + selectDescriptor("contextWindow", [ + { id: "200k", label: "200k", isDefault: true }, + { id: "1m", label: "1M" }, + ]), + selectDescriptor("agent", [ + { id: "build", label: "Build", isDefault: true }, + { id: "plan", label: "Plan" }, + ]), + ]), + prompt: "", + modelOptions: selections(["agent", "plan"]), + }); + + expect(state.promptEffort).toBe("high"); + expect(state.modelOptionsForDispatch).toEqual( + selections(["effort", "high"], ["contextWindow", "200k"], ["agent", "plan"]), + ); + }); + + it("returns undefined dispatch options when the model declares no descriptors", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([]), + prompt: "", + modelOptions: selections(["anything", "value"]), + }); + + expect(state).toEqual({ + provider: PROVIDER, + promptEffort: null, + modelOptionsForDispatch: undefined, + }); + }); + + it("adds ultrathink class names when the prompt triggers a promptInjectedValues descriptor", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor( + "effort", + [ + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + { id: "ultrathink", label: "Ultrathink" }, + ], + ["ultrathink"], + ), + ]), + prompt: "Ultrathink:\nInvestigate this failure", + modelOptions: selections(["effort", "medium"]), + }); + + expect(state).toEqual({ + provider: PROVIDER, + promptEffort: "medium", + modelOptionsForDispatch: selections(["effort", "medium"]), + ...ULTRATHINK_FRAME_CLASSES, + }); + }); + + it("does not add ultrathink class names when the descriptor has no promptInjectedValues", () => { + const state = getComposerProviderState({ + provider: PROVIDER, + model: MODEL, + models: modelWith([ + selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), + ]), + prompt: "Ultrathink:\nInvestigate this failure", + modelOptions: undefined, + }); + + expect(state).not.toHaveProperty("composerFrameClassName"); + expect(state).not.toHaveProperty("composerSurfaceClassName"); + expect(state).not.toHaveProperty("modelPickerIconClassName"); + }); +}); + +describe("provider traits render guards", () => { + it("returns null when no thread target is provided", () => { + const models = modelWith([ + selectDescriptor("effort", [{ id: "high", label: "High", isDefault: true }]), + ]); + const args = { + provider: PROVIDER, + model: MODEL, + models, + modelOptions: undefined, + prompt: "", + onPromptChange: () => {}, + }; + + expect(renderProviderTraitsPicker(args)).toBeNull(); + expect(renderProviderTraitsMenuContent(args)).toBeNull(); + }); +}); diff --git a/apps/web/src/components/chat/composerProviderState.tsx b/apps/web/src/components/chat/composerProviderState.tsx new file mode 100644 index 00000000000..b817786563a --- /dev/null +++ b/apps/web/src/components/chat/composerProviderState.tsx @@ -0,0 +1,108 @@ +import { + type ProviderKind, + type ProviderOptionSelection, + type ScopedThreadRef, + type ServerProviderModel, +} from "@t3tools/contracts"; +import { + buildProviderOptionSelectionsFromDescriptors, + getProviderOptionCurrentValue, + getProviderOptionDescriptors, + isClaudeUltrathinkPrompt, +} from "@t3tools/shared/model"; +import type { ReactNode } from "react"; + +import type { DraftId } from "../../composerDraftStore"; +import { getProviderModelCapabilities } from "../../providerModels"; +import { shouldRenderTraitsControls, TraitsMenuContent, TraitsPicker } from "./TraitsPicker"; + +export type ComposerProviderStateInput = { + provider: ProviderKind; + model: string; + models: ReadonlyArray; + prompt: string; + modelOptions: ReadonlyArray | null | undefined; +}; + +export type ComposerProviderState = { + provider: ProviderKind; + promptEffort: string | null; + modelOptionsForDispatch: ReadonlyArray | undefined; + composerFrameClassName?: string; + composerSurfaceClassName?: string; + modelPickerIconClassName?: string; +}; + +type TraitsRenderInput = { + provider: ProviderKind; + threadRef?: ScopedThreadRef; + draftId?: DraftId; + model: string; + models: ReadonlyArray; + modelOptions: ReadonlyArray | undefined; + prompt: string; + onPromptChange: (prompt: string) => void; +}; + +export function getComposerProviderState(input: ComposerProviderStateInput): ComposerProviderState { + const { provider, model, models, prompt, modelOptions } = input; + const caps = getProviderModelCapabilities(models, model, provider); + const descriptors = getProviderOptionDescriptors({ caps, selections: modelOptions }); + const primarySelectDescriptor = descriptors.find( + (descriptor): descriptor is Extract<(typeof descriptors)[number], { type: "select" }> => + descriptor.type === "select", + ); + const primaryValue = getProviderOptionCurrentValue(primarySelectDescriptor ?? null); + const promptEffort = typeof primaryValue === "string" ? primaryValue : null; + const ultrathinkActive = + (primarySelectDescriptor?.promptInjectedValues?.length ?? 0) > 0 && + isClaudeUltrathinkPrompt(prompt); + + return { + provider, + promptEffort, + modelOptionsForDispatch: buildProviderOptionSelectionsFromDescriptors(descriptors), + ...(ultrathinkActive + ? { + composerFrameClassName: "ultrathink-frame", + composerSurfaceClassName: "shadow-[0_0_0_1px_rgba(255,255,255,0.04)_inset]", + modelPickerIconClassName: "ultrathink-chroma", + } + : {}), + }; +} + +function renderTraitsControl( + Component: typeof TraitsMenuContent | typeof TraitsPicker, + input: TraitsRenderInput, +): ReactNode { + const { provider, threadRef, draftId, model, models, modelOptions, prompt, onPromptChange } = + input; + const hasTarget = threadRef !== undefined || draftId !== undefined; + if ( + !hasTarget || + !shouldRenderTraitsControls({ provider, models, model, modelOptions, prompt }) + ) { + return null; + } + return ( + + ); +} + +export function renderProviderTraitsMenuContent(input: TraitsRenderInput): ReactNode { + return renderTraitsControl(TraitsMenuContent, input); +} + +export function renderProviderTraitsPicker(input: TraitsRenderInput): ReactNode { + return renderTraitsControl(TraitsPicker, input); +} diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index b1e7cf81f6f..1e8cffd0c4a 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -11,7 +11,6 @@ import { import { useQueryClient } from "@tanstack/react-query"; import { type ReactNode, useCallback, useMemo, useRef, useState } from "react"; import { - PROVIDER_DISPLAY_NAMES, type DesktopUpdateChannel, type ScopedThreadRef, type ProviderKind, @@ -20,8 +19,7 @@ import { } from "@t3tools/contracts"; import { scopeThreadRef } from "@t3tools/client-runtime"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; -import { normalizeModelSlug } from "@t3tools/shared/model"; -import { createModelSelection } from "@t3tools/shared/model"; +import { createModelSelection, normalizeModelSlug } from "@t3tools/shared/model"; import { Equal } from "effect"; import { APP_VERSION } from "../../branding"; import { @@ -79,6 +77,7 @@ import { useServerObservability, useServerProviders, } from "../../rpc/serverState"; +import { formatProviderKindLabel } from "../../providerModels"; const THEME_OPTIONS = [ { @@ -1172,7 +1171,9 @@ export function GeneralSettingsPanel() { const customModelInput = customModelInputByProvider[providerCard.provider]; const customModelError = customModelErrorByProvider[providerCard.provider] ?? null; const providerDisplayName = - PROVIDER_DISPLAY_NAMES[providerCard.provider] ?? providerCard.title; + providerCard.liveProvider?.displayName?.trim() || + providerCard.title || + formatProviderKindLabel(providerCard.provider); return (
@@ -1471,11 +1472,22 @@ export function GeneralSettingsPanel() { {providerCard.models.map((model) => { const caps = model.capabilities; const capLabels: string[] = []; - if (caps?.supportsFastMode) capLabels.push("Fast mode"); - if (caps?.supportsThinkingToggle) capLabels.push("Thinking"); + const descriptors = caps?.optionDescriptors ?? []; + if (descriptors.some((descriptor) => descriptor.id === "fastMode")) { + capLabels.push("Fast mode"); + } + if (descriptors.some((descriptor) => descriptor.id === "thinking")) { + capLabels.push("Thinking"); + } if ( - caps?.reasoningEffortLevels && - caps.reasoningEffortLevels.length > 0 + descriptors.some( + (descriptor) => + descriptor.type === "select" && + (descriptor.id === "reasoningEffort" || + descriptor.id === "effort" || + descriptor.id === "reasoning" || + descriptor.id === "variant"), + ) ) { capLabels.push("Reasoning"); } diff --git a/apps/web/src/composerDraftStore.test.ts b/apps/web/src/composerDraftStore.test.ts index d789d7d510a..9416b42ff93 100644 --- a/apps/web/src/composerDraftStore.test.ts +++ b/apps/web/src/composerDraftStore.test.ts @@ -10,8 +10,38 @@ import { ProjectId, ThreadId, type ModelSelection, - type ProviderModelOptions, + type ProviderKind, + type ProviderOptionSelection, } from "@t3tools/contracts"; +import { createModelSelection } from "@t3tools/shared/model"; + +type ProviderOptionSelectionBag = ReadonlyArray; +type ProviderOptionSelectionsByProvider = Partial>; + +function toSelections( + options: Record | undefined, +): ReadonlyArray { + const result: Array = []; + if (!options) return result; + for (const [id, value] of Object.entries(options)) { + if (typeof value === "string" || typeof value === "boolean") { + result.push({ id, value }); + } + } + return result; +} + +function selectionsByProvider( + options: Partial>>, +): ProviderOptionSelectionsByProvider { + const result: ProviderOptionSelectionsByProvider = {}; + for (const [provider, bag] of Object.entries(options) as Array< + [ProviderKind, Record] + >) { + result[provider] = toSelections(bag); + } + return result; +} import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { @@ -93,17 +123,15 @@ function resetComposerDraftStore() { function modelSelection( provider: "codex" | "claudeAgent" | "cursor", model: string, - options?: ModelSelection["options"], + options?: Record, ): ModelSelection { - return { - provider, - model, - ...(options ? { options } : {}), - } as ModelSelection; + return createModelSelection(provider, model, toSelections(options)); } -function providerModelOptions(options: ProviderModelOptions): ProviderModelOptions { - return options; +function providerModelOptions( + options: Partial>>, +): ProviderOptionSelectionsByProvider { + return selectionsByProvider(options); } const TEST_ENVIRONMENT_ID = EnvironmentId.make("environment-local"); @@ -941,14 +969,9 @@ describe("composerDraftStore modelSelection", () => { }), ); - store.setProviderModelOptions( - threadRef, - "claudeAgent", - { - thinking: false, - }, - { persistSticky: true }, - ); + store.setProviderModelOptions(threadRef, "claudeAgent", toSelections({ thinking: false }), { + persistSticky: true, + }); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.claudeAgent).toEqual( modelSelection("claudeAgent", "claude-opus-4-6", { @@ -972,9 +995,7 @@ describe("composerDraftStore modelSelection", () => { }), ); - store.setProviderModelOptions(threadRef, "claudeAgent", { - thinking: true, - }); + store.setProviderModelOptions(threadRef, "claudeAgent", toSelections({ thinking: true })); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.claudeAgent).toEqual( modelSelection("claudeAgent", "claude-opus-4-6", { @@ -989,10 +1010,11 @@ describe("composerDraftStore modelSelection", () => { store.setModelSelection(threadRef, modelSelection("codex", "gpt-5.4", { fastMode: true })); - store.setProviderModelOptions(threadRef, "codex", { - reasoningEffort: "high", - fastMode: false, - }); + store.setProviderModelOptions( + threadRef, + "codex", + toSelections({ reasoningEffort: "high", fastMode: false }), + ); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.codex).toEqual( modelSelection("codex", "gpt-5.4", { @@ -1014,11 +1036,11 @@ describe("composerDraftStore modelSelection", () => { }), ); - store.setProviderModelOptions(threadRef, "cursor", { - reasoning: "medium", - fastMode: false, - thinking: true, - }); + store.setProviderModelOptions( + threadRef, + "cursor", + toSelections({ reasoning: "medium", fastMode: false, thinking: true }), + ); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.cursor).toEqual( modelSelection("cursor", "claude-opus-4-6", { @@ -1032,17 +1054,10 @@ describe("composerDraftStore modelSelection", () => { it("preserves the selected Cursor model when only traits change", () => { const store = useComposerDraftStore.getState(); - store.setProviderModelOptions( - threadRef, - "cursor", - { - reasoning: "high", - }, - { - model: "gpt-5.4", - persistSticky: true, - }, - ); + store.setProviderModelOptions(threadRef, "cursor", toSelections({ reasoning: "high" }), { + model: "gpt-5.4", + persistSticky: true, + }); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.cursor).toEqual( modelSelection("cursor", "gpt-5.4", { @@ -1067,9 +1082,7 @@ describe("composerDraftStore modelSelection", () => { modelSelection("claudeAgent", "claude-opus-4-6", { effort: "max" }), ); - store.setProviderModelOptions(threadRef, "claudeAgent", { - thinking: false, - }); + store.setProviderModelOptions(threadRef, "claudeAgent", toSelections({ thinking: false })); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.claudeAgent).toEqual( modelSelection("claudeAgent", "claude-opus-4-6", { @@ -1097,8 +1110,13 @@ describe("composerDraftStore modelSelection", () => { store.setModelOptions(threadRef, providerModelOptions({ codex: { reasoningEffort: "xhigh" } })); const draft = draftFor(threadId, TEST_ENVIRONMENT_ID); - expect(draft?.modelSelectionByProvider.codex?.options).toEqual({ reasoningEffort: "xhigh" }); - expect(draft?.modelSelectionByProvider.claudeAgent?.options).toEqual({ effort: "max" }); + expect(draft?.modelSelectionByProvider.codex?.options).toEqual( + createModelSelection("codex", "gpt-5.4", toSelections({ reasoningEffort: "xhigh" })).options, + ); + expect(draft?.modelSelectionByProvider.claudeAgent?.options).toEqual( + createModelSelection("claudeAgent", "claude-opus-4-6", toSelections({ effort: "max" })) + .options, + ); }); it("preserves other provider options when switching the active model selection", () => { @@ -1118,7 +1136,9 @@ describe("composerDraftStore modelSelection", () => { expect(draft?.modelSelectionByProvider.claudeAgent).toEqual( modelSelection("claudeAgent", "claude-opus-4-6", { effort: "max" }), ); - expect(draft?.modelSelectionByProvider.codex?.options).toEqual({ fastMode: true }); + expect(draft?.modelSelectionByProvider.codex?.options).toEqual( + createModelSelection("codex", "gpt-5.4", toSelections({ fastMode: true })).options, + ); expect(draft?.activeProvider).toBe("claudeAgent"); }); @@ -1127,14 +1147,9 @@ describe("composerDraftStore modelSelection", () => { store.setModelSelection(threadRef, modelSelection("codex", "gpt-5.4")); - store.setProviderModelOptions( - threadRef, - "codex", - { - fastMode: true, - }, - { persistSticky: true }, - ); + store.setProviderModelOptions(threadRef, "codex", toSelections({ fastMode: true }), { + persistSticky: true, + }); expect(useComposerDraftStore.getState().stickyModelSelectionByProvider.codex).toEqual( modelSelection("codex", "gpt-5.4", { @@ -1154,14 +1169,9 @@ describe("composerDraftStore modelSelection", () => { modelSelection("claudeAgent", "claude-opus-4-6", { effort: "max" }), ); - store.setProviderModelOptions( - threadRef, - "claudeAgent", - { - thinking: false, - }, - { persistSticky: false }, - ); + store.setProviderModelOptions(threadRef, "claudeAgent", toSelections({ thinking: false }), { + persistSticky: false, + }); expect(draftFor(threadId, TEST_ENVIRONMENT_ID)?.modelSelectionByProvider.claudeAgent).toEqual( modelSelection("claudeAgent", "claude-opus-4-6", { @@ -1279,12 +1289,15 @@ describe("composerDraftStore provider-scoped option updates", () => { reasoningEffort: "medium", }), ); - store.setProviderModelOptions(threadRef, "claudeAgent", { effort: "max" }); + store.setProviderModelOptions(threadRef, "claudeAgent", toSelections({ effort: "max" })); const draft = draftFor(threadId, TEST_ENVIRONMENT_ID); expect(draft?.modelSelectionByProvider.codex).toEqual( modelSelection("codex", "gpt-5.3-codex", { reasoningEffort: "medium" }), ); - expect(draft?.modelSelectionByProvider.claudeAgent?.options).toEqual({ effort: "max" }); + expect(draft?.modelSelectionByProvider.claudeAgent?.options).toEqual( + createModelSelection("claudeAgent", "claude-opus-4-6", toSelections({ effort: "max" })) + .options, + ); expect(draft?.activeProvider).toBe("codex"); }); }); diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index 36304fc0236..cdd11a77b19 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -1,16 +1,11 @@ import { - CURSOR_REASONING_OPTIONS, DEFAULT_MODEL_BY_PROVIDER, - type CursorModelOptions, - type CursorReasoningOption, - ClaudeAgentEffort, - CodexReasoningEffort, type EnvironmentId, ModelSelection, ProjectId, ProviderInteractionMode, ProviderKind, - ProviderModelOptions, + ProviderOptionSelection, RuntimeMode, type ServerProvider, type ScopedProjectRef, @@ -46,7 +41,7 @@ import { getDefaultServerModel } from "./providerModels"; import { UnifiedSettings } from "@t3tools/contracts/settings"; export const COMPOSER_DRAFT_STORAGE_KEY = "t3code:composer-drafts:v1"; -const COMPOSER_DRAFT_STORAGE_VERSION = 5; +const COMPOSER_DRAFT_STORAGE_VERSION = 6; const DraftThreadEnvModeSchema = Schema.Literals(["local", "worktree"]); const isRuntimeMode = Schema.is(RuntimeMode); export type DraftThreadEnvMode = typeof DraftThreadEnvModeSchema.Type; @@ -106,23 +101,30 @@ const PersistedComposerThreadDraftState = Schema.Struct({ }); type PersistedComposerThreadDraftState = typeof PersistedComposerThreadDraftState.Type; -const LegacyCodexFields = Schema.Struct({ - effort: Schema.optionalKey(CodexReasoningEffort), - codexFastMode: Schema.optionalKey(Schema.Boolean), - serviceTier: Schema.optionalKey(Schema.String), -}); -type LegacyCodexFields = typeof LegacyCodexFields.Type; +/** + * Per-provider record of generic option selections. Used as a transient + * representation when migrating legacy v2 storage payloads and when + * deriving per-provider option bundles for downstream consumers. + */ +type ProviderOptionSelectionsByProvider = Partial< + Record> +>; -const LegacyThreadModelFields = Schema.Struct({ - provider: Schema.optionalKey(ProviderKind), - model: Schema.optionalKey(Schema.String), - modelOptions: Schema.optionalKey(Schema.NullOr(ProviderModelOptions)), -}); -type LegacyThreadModelFields = typeof LegacyThreadModelFields.Type; +type LegacyCodexFields = { + effort?: unknown; + codexFastMode?: unknown; + serviceTier?: unknown; +}; + +type LegacyThreadModelFields = { + provider?: unknown; + model?: unknown; + modelOptions?: unknown; +}; type LegacyV2ThreadDraftFields = { modelSelection?: ModelSelection | null; - modelOptions?: ProviderModelOptions | null; + modelOptions?: unknown; }; type LegacyPersistedComposerThreadDraftState = PersistedComposerThreadDraftState & @@ -130,16 +132,15 @@ type LegacyPersistedComposerThreadDraftState = PersistedComposerThreadDraftState LegacyThreadModelFields & LegacyV2ThreadDraftFields; -const LegacyStickyModelFields = Schema.Struct({ - stickyProvider: Schema.optionalKey(ProviderKind), - stickyModel: Schema.optionalKey(Schema.String), - stickyModelOptions: Schema.optionalKey(Schema.NullOr(ProviderModelOptions)), -}); -type LegacyStickyModelFields = typeof LegacyStickyModelFields.Type; +type LegacyStickyModelFields = { + stickyProvider?: unknown; + stickyModel?: unknown; + stickyModelOptions?: unknown; +}; type LegacyV2StoreFields = { stickyModelSelection?: ModelSelection | null; - stickyModelOptions?: ProviderModelOptions | null; + stickyModelOptions?: unknown; projectDraftThreadIdByProjectId?: Record | null; draftsByThreadId?: Record | null; draftThreadsByThreadId?: Record | null; @@ -334,15 +335,19 @@ interface ComposerDraftStoreState { threadRef: ComposerThreadTarget, modelSelection: ModelSelection | null | undefined, ) => void; + /** Replace the model options for one or more providers in the draft. */ setModelOptions: ( threadRef: ComposerThreadTarget, - modelOptions: ProviderModelOptions | null | undefined, + modelOptions: + | Partial>> + | null + | undefined, ) => void; applyStickyState: (threadRef: ComposerThreadTarget) => void; setProviderModelOptions: ( threadRef: ComposerThreadTarget, provider: ProviderKind, - nextProviderOptions: ProviderModelOptions[ProviderKind] | null | undefined, + nextProviderOptions: ReadonlyArray | null | undefined, options?: { model?: string | null | undefined; persistSticky?: boolean; @@ -379,7 +384,7 @@ interface ComposerDraftStoreState { export interface EffectiveComposerModelState { selectedModel: string; - modelOptions: ProviderModelOptions | null; + modelOptions: ProviderOptionSelectionsByProvider | null; } interface ComposerDraftModelState { @@ -387,29 +392,48 @@ interface ComposerDraftModelState { modelSelectionByProvider: Partial>; } -function providerModelOptionsFromSelection( +function providerSelectionsFromModelSelection( modelSelection: ModelSelection | null | undefined, -): ProviderModelOptions | null { - if (!modelSelection?.options) { +): ProviderOptionSelectionsByProvider | null { + if (!modelSelection) { return null; } - - return { - [modelSelection.provider]: modelSelection.options, - }; + const options = modelSelection.options; + if (!options || options.length === 0) { + return null; + } + return { [modelSelection.provider]: options } as ProviderOptionSelectionsByProvider; } function modelSelectionByProviderToOptions( map: Partial> | null | undefined, -): ProviderModelOptions | null { +): ProviderOptionSelectionsByProvider | null { if (!map) return null; - const result: Record = {}; + const result: Partial>> = {}; for (const [provider, selection] of Object.entries(map)) { - if (selection?.options) { - result[provider] = selection.options; + if (selection?.options && selection.options.length > 0) { + result[provider as ProviderKind] = selection.options; } } - return Object.keys(result).length > 0 ? (result as ProviderModelOptions) : null; + return Object.keys(result).length > 0 ? result : null; +} + +function cloneModelSelection(selection: ModelSelection): DeepMutable { + return { + ...selection, + ...(selection.options ? { options: selection.options.map((option) => ({ ...option })) } : {}), + } as DeepMutable; +} + +function cloneModelSelectionByProvider( + selections: Partial>, +): DeepMutable>> { + return Object.fromEntries( + Object.entries(selections).map(([provider, selection]) => [ + provider, + selection ? cloneModelSelection(selection) : selection, + ]), + ) as DeepMutable>>; } const EMPTY_PERSISTED_DRAFT_STORE_STATE = Object.freeze({ @@ -536,149 +560,92 @@ function normalizeProviderKind(value: unknown): ProviderKind | null { : null; } +/** + * Coerce an unknown value into a `ReadonlyArray`. + * Accepts either: + * - the v3 representation: an array of `{ id, value }` entries + * - the legacy v2 representation: a record of `{ id: string | boolean }` + * + * Validation is intentionally permissive: descriptors are the source of truth + * for which option ids are meaningful for a given provider/model. Anything + * outside the descriptor list is harmless trailing data and will simply be + * ignored downstream. + */ +function coerceProviderOptionSelections( + value: unknown, +): ReadonlyArray | undefined { + if (Array.isArray(value)) { + const out: ProviderOptionSelection[] = []; + for (const entry of value) { + if (!entry || typeof entry !== "object") continue; + const record = entry as Record; + const id = record.id; + const optionValue = record.value; + if (typeof id !== "string" || id.length === 0) continue; + if (typeof optionValue === "string" || typeof optionValue === "boolean") { + out.push({ id, value: optionValue }); + } + } + return out.length > 0 ? out : undefined; + } + if (value && typeof value === "object") { + const record = value as Record; + const out: ProviderOptionSelection[] = []; + for (const [id, raw] of Object.entries(record)) { + if (typeof raw === "string" || typeof raw === "boolean") { + out.push({ id, value: raw }); + } + } + return out.length > 0 ? out : undefined; + } + return undefined; +} + +/** + * Normalize a per-provider options bag from either the v3 or legacy v2 shape. + * + * `provider` and `legacy` parameters are migration-only inputs used to + * recover legacy codex fields (effort/codexFastMode/serviceTier) that lived + * directly on the draft instead of inside `modelOptions.codex`. + */ function normalizeProviderModelOptions( value: unknown, provider?: ProviderKind | null, legacy?: LegacyCodexFields, -): ProviderModelOptions | null { +): ProviderOptionSelectionsByProvider | null { const candidate = value && typeof value === "object" ? (value as Record) : null; - const codexCandidate = - candidate?.codex && typeof candidate.codex === "object" - ? (candidate.codex as Record) - : null; - const claudeCandidate = - candidate?.claudeAgent && typeof candidate.claudeAgent === "object" - ? (candidate.claudeAgent as Record) - : null; - const cursorCandidate = - candidate?.cursor && typeof candidate.cursor === "object" - ? (candidate.cursor as Record) - : null; - const openCodeCandidate = - candidate?.opencode && typeof candidate.opencode === "object" - ? (candidate.opencode as Record) - : null; - - const isCodexReasoningEffort = Schema.is(CodexReasoningEffort); - const isClaudeAgentEffort = Schema.is(ClaudeAgentEffort); - - const codexReasoningEffort = isCodexReasoningEffort(codexCandidate?.reasoningEffort) - ? codexCandidate.reasoningEffort - : provider === "codex" - ? isCodexReasoningEffort(legacy?.effort) - ? legacy.effort - : undefined - : undefined; - const codexFastMode = - codexCandidate?.fastMode === true - ? true - : codexCandidate?.fastMode === false - ? false - : (provider === "codex" && legacy?.codexFastMode === true) || - (typeof legacy?.serviceTier === "string" && legacy.serviceTier === "fast") - ? true - : undefined; - const codex = - codexReasoningEffort !== undefined || codexFastMode !== undefined - ? { - ...(codexReasoningEffort !== undefined ? { reasoningEffort: codexReasoningEffort } : {}), - ...(codexFastMode !== undefined ? { fastMode: codexFastMode } : {}), - } - : undefined; - - const claudeThinking = - claudeCandidate?.thinking === true - ? true - : claudeCandidate?.thinking === false - ? false - : undefined; - const claudeEffort = isClaudeAgentEffort(claudeCandidate?.effort) - ? claudeCandidate.effort - : undefined; - const claudeFastMode = - claudeCandidate?.fastMode === true - ? true - : claudeCandidate?.fastMode === false - ? false - : undefined; - const claudeContextWindow = - typeof claudeCandidate?.contextWindow === "string" && claudeCandidate.contextWindow.length > 0 - ? claudeCandidate.contextWindow - : undefined; - const claude = - claudeThinking !== undefined || - claudeEffort !== undefined || - claudeFastMode !== undefined || - claudeContextWindow !== undefined - ? { - ...(claudeThinking !== undefined ? { thinking: claudeThinking } : {}), - ...(claudeEffort !== undefined ? { effort: claudeEffort } : {}), - ...(claudeFastMode !== undefined ? { fastMode: claudeFastMode } : {}), - ...(claudeContextWindow !== undefined ? { contextWindow: claudeContextWindow } : {}), - } - : undefined; - - const cursorReasoningRaw = cursorCandidate?.reasoning; - const cursorReasoning: CursorReasoningOption | undefined = - typeof cursorReasoningRaw === "string" && - (CURSOR_REASONING_OPTIONS as readonly string[]).includes(cursorReasoningRaw) - ? (cursorReasoningRaw as CursorReasoningOption) - : undefined; - const cursorFastMode = - cursorCandidate?.fastMode === true - ? true - : cursorCandidate?.fastMode === false - ? false - : undefined; - const cursorThinking = - cursorCandidate?.thinking === true - ? true - : cursorCandidate?.thinking === false - ? false - : undefined; - const cursorContextWindow = - typeof cursorCandidate?.contextWindow === "string" && cursorCandidate.contextWindow.length > 0 - ? cursorCandidate.contextWindow - : undefined; - - const cursor: CursorModelOptions | undefined = - cursorCandidate !== null - ? (() => { - const nextCursor = { - ...(cursorReasoning ? { reasoning: cursorReasoning } : {}), - ...(cursorFastMode !== undefined ? { fastMode: cursorFastMode } : {}), - ...(cursorThinking !== undefined ? { thinking: cursorThinking } : {}), - ...(cursorContextWindow !== undefined ? { contextWindow: cursorContextWindow } : {}), - } satisfies CursorModelOptions; - return Object.keys(nextCursor).length > 0 ? nextCursor : undefined; - })() - : undefined; - - const openCodeVariant = - typeof openCodeCandidate?.variant === "string" && openCodeCandidate.variant.length > 0 - ? openCodeCandidate.variant - : undefined; - const openCodeAgent = - typeof openCodeCandidate?.agent === "string" && openCodeCandidate.agent.length > 0 - ? openCodeCandidate.agent - : undefined; - const opencode = - openCodeVariant !== undefined || openCodeAgent !== undefined - ? { - ...(openCodeVariant !== undefined ? { variant: openCodeVariant } : {}), - ...(openCodeAgent !== undefined ? { agent: openCodeAgent } : {}), - } - : undefined; + const result: Partial>> = {}; + for (const providerKey of ["codex", "claudeAgent", "cursor", "opencode"] as const) { + const selections = coerceProviderOptionSelections(candidate?.[providerKey]); + if (selections) { + result[providerKey] = selections; + } + } - if (!codex && !claude && cursor === undefined && !opencode) { - return null; + // Recover legacy codex fields that lived outside modelOptions. + if (provider === "codex" && legacy) { + const codexExtras: ProviderOptionSelection[] = []; + if (typeof legacy.effort === "string" && legacy.effort.length > 0) { + codexExtras.push({ id: "reasoningEffort", value: legacy.effort }); + } + const fastMode = + legacy.codexFastMode === true || + (typeof legacy.serviceTier === "string" && legacy.serviceTier === "fast"); + if (fastMode) { + codexExtras.push({ id: "fastMode", value: true }); + } + if (codexExtras.length > 0) { + const existing = result.codex ?? []; + const existingIds = new Set(existing.map((entry) => entry.id)); + const merged = [...existing]; + for (const extra of codexExtras) { + if (!existingIds.has(extra.id)) merged.push(extra); + } + result.codex = merged; + } } - return { - ...(codex ? { codex } : {}), - ...(claude ? { claudeAgent: claude } : {}), - ...(cursor !== undefined ? { cursor } : {}), - ...(opencode ? { opencode } : {}), - }; + + return Object.keys(result).length > 0 ? result : null; } function normalizeModelSelection( @@ -703,6 +670,10 @@ function normalizeModelSelection( if (!model) { return null; } + if (Array.isArray(candidate?.options)) { + const selections = coerceProviderOptionSelections(candidate.options); + return createModelSelection(provider, model, selections); + } const modelOptions = normalizeProviderModelOptions( candidate?.options ? { [provider]: candidate.options } : legacy?.modelOptions, provider, @@ -716,7 +687,7 @@ function normalizeModelSelection( function legacySyncModelSelectionOptions( modelSelection: ModelSelection | null, - modelOptions: ProviderModelOptions | null | undefined, + modelOptions: ProviderOptionSelectionsByProvider | null | undefined, ): ModelSelection | null { if (modelSelection === null) { return null; @@ -727,9 +698,9 @@ function legacySyncModelSelectionOptions( function legacyMergeModelSelectionIntoProviderModelOptions( modelSelection: ModelSelection | null, - currentModelOptions: ProviderModelOptions | null | undefined, -): ProviderModelOptions | null { - if (modelSelection?.options === undefined) { + currentModelOptions: ProviderOptionSelectionsByProvider | null | undefined, +): ProviderOptionSelectionsByProvider | null { + if (!modelSelection?.options || modelSelection.options.length === 0) { return normalizeProviderModelOptions(currentModelOptions); } return legacyReplaceProviderModelOptions( @@ -740,35 +711,30 @@ function legacyMergeModelSelectionIntoProviderModelOptions( } function legacyReplaceProviderModelOptions( - currentModelOptions: ProviderModelOptions | null | undefined, + currentModelOptions: ProviderOptionSelectionsByProvider | null | undefined, provider: ProviderKind, - nextProviderOptions: ProviderModelOptions[ProviderKind] | null | undefined, -): ProviderModelOptions | null { + nextProviderOptions: ReadonlyArray | null | undefined, +): ProviderOptionSelectionsByProvider | null { const { [provider]: _discardedProviderModelOptions, ...otherProviderModelOptions } = currentModelOptions ?? {}; - const normalizedNextProviderOptions = normalizeProviderModelOptions( - { [provider]: nextProviderOptions }, - provider, - ); - - return normalizeProviderModelOptions({ - ...otherProviderModelOptions, - ...(normalizedNextProviderOptions ? normalizedNextProviderOptions : {}), - }); + const merged: ProviderOptionSelectionsByProvider = { ...otherProviderModelOptions }; + if (nextProviderOptions && nextProviderOptions.length > 0) { + merged[provider] = nextProviderOptions; + } + return Object.keys(merged).length > 0 ? merged : null; } // ── New helpers for the consolidated representation ──────────────────── function legacyToModelSelectionByProvider( modelSelection: ModelSelection | null, - modelOptions: ProviderModelOptions | null | undefined, + modelOptions: ProviderOptionSelectionsByProvider | null | undefined, ): Partial> { const result: Partial> = {}; - // Add entries from the options bag (for non-active providers) if (modelOptions) { for (const provider of ["codex", "claudeAgent", "cursor", "opencode"] as const) { const options = modelOptions[provider]; - if (options && Object.keys(options).length > 0) { + if (options && options.length > 0) { result[provider] = createModelSelection( provider, modelSelection?.provider === provider @@ -779,7 +745,6 @@ function legacyToModelSelectionByProvider( } } } - // Add/overwrite the active selection (it's authoritative for its provider) if (modelSelection) { result[modelSelection.provider] = modelSelection; } @@ -813,8 +778,8 @@ export function deriveEffectiveComposerModelState(input: { : baseModel; const modelOptions = modelSelectionByProviderToOptions(input.draft?.modelSelectionByProvider) ?? - providerModelOptionsFromSelection(input.threadModelSelection) ?? - providerModelOptionsFromSelection(input.projectModelSelection) ?? + providerSelectionsFromModelSelection(input.threadModelSelection) ?? + providerSelectionsFromModelSelection(input.projectModelSelection) ?? null; return { @@ -1425,7 +1390,7 @@ function normalizePersistedDraftsByThreadId( { provider: legacyDraftCandidate.provider, model: legacyDraftCandidate.model, - modelOptions: normalizedModelOptions ?? legacyDraftCandidate.modelOptions, + modelOptions: normalizedModelOptions ?? (legacyDraftCandidate.modelOptions as unknown), legacyCodex: legacyDraftCandidate, }, ); @@ -1472,7 +1437,12 @@ function normalizePersistedDraftsByThreadId( prompt, attachments, ...(terminalContexts.length > 0 ? { terminalContexts } : {}), - ...(hasModelData ? { modelSelectionByProvider, activeProvider } : {}), + ...(hasModelData + ? { + modelSelectionByProvider: cloneModelSelectionByProvider(modelSelectionByProvider), + activeProvider, + } + : {}), ...(runtimeMode ? { runtimeMode } : {}), ...(interactionMode ? { interactionMode } : {}), }; @@ -1573,7 +1543,7 @@ function partializeComposerDraftStoreState( : {}), ...(hasModelData ? { - modelSelectionByProvider: draft.modelSelectionByProvider, + modelSelectionByProvider: cloneModelSelectionByProvider(draft.modelSelectionByProvider), activeProvider: draft.activeProvider, } : {}), @@ -2304,27 +2274,24 @@ const composerDraftStore = create()( if (threadKey.length === 0) { return; } - const normalizedOpts = normalizeProviderModelOptions(modelOptions); set((state) => { const existing = state.draftsByThreadKey[threadKey]; - if (!existing && normalizedOpts === null) { + if (!existing && (!modelOptions || Object.keys(modelOptions).length === 0)) { return state; } const base = existing ?? createEmptyThreadDraft(); const nextMap = { ...base.modelSelectionByProvider }; for (const provider of ["codex", "claudeAgent", "cursor", "opencode"] as const) { - // Only touch providers explicitly present in the input - if (!normalizedOpts || !(provider in normalizedOpts)) continue; - const opts = normalizedOpts[provider]; + if (!modelOptions || !(provider in modelOptions)) continue; + const opts = modelOptions[provider]; const current = nextMap[provider]; - if (opts) { + if (opts && opts.length > 0) { nextMap[provider] = createModelSelection( provider, current?.model ?? DEFAULT_MODEL_BY_PROVIDER[provider], opts, ); } else if (current?.options) { - // Remove options but keep the selection const { options: _, ...rest } = current; nextMap[provider] = rest as ModelSelection; } @@ -2357,12 +2324,8 @@ const composerDraftStore = create()( const fallbackModel = normalizeModelSlug(options?.model, normalizedProvider) ?? DEFAULT_MODEL_BY_PROVIDER[normalizedProvider]; - // Normalize just this provider's options - const normalizedOpts = normalizeProviderModelOptions( - { [normalizedProvider]: nextProviderOptions }, - normalizedProvider, - ); - const providerOpts = normalizedOpts?.[normalizedProvider]; + const providerOpts = + nextProviderOptions && nextProviderOptions.length > 0 ? nextProviderOptions : undefined; set((state) => { const existing = state.draftsByThreadKey[threadKey]; @@ -2377,7 +2340,7 @@ const composerDraftStore = create()( currentForProvider?.model ?? fallbackModel, providerOpts, ); - } else if (currentForProvider?.options) { + } else if (currentForProvider && (currentForProvider.options?.length ?? 0) > 0) { const { options: _, ...rest } = currentForProvider; nextMap[normalizedProvider] = rest as ModelSelection; } @@ -2397,7 +2360,7 @@ const composerDraftStore = create()( stickyBase.model, providerOpts, ); - } else if (stickyBase.options) { + } else if ((stickyBase.options?.length ?? 0) > 0) { const { options: _, ...rest } = stickyBase; nextStickyMap[normalizedProvider] = rest as ModelSelection; } diff --git a/apps/web/src/modelSelection.ts b/apps/web/src/modelSelection.ts index 2ffa8e20140..80e9e241e78 100644 --- a/apps/web/src/modelSelection.ts +++ b/apps/web/src/modelSelection.ts @@ -9,7 +9,7 @@ import { normalizeModelSlug, resolveSelectableModel, } from "@t3tools/shared/model"; -import { getComposerProviderState } from "./components/chat/composerProviderRegistry"; +import { getComposerProviderState } from "./components/chat/composerProviderState"; import { UnifiedSettings } from "@t3tools/contracts/settings"; import { getDefaultServerModel, @@ -21,14 +21,6 @@ import { ModelEsque } from "./components/chat/providerIconUtils"; const MAX_CUSTOM_MODEL_COUNT = 32; export const MAX_CUSTOM_MODEL_LENGTH = 256; -export type ProviderCustomModelConfig = { - provider: ProviderKind; - title: string; - description: string; - placeholder: string; - example: string; -}; - export interface AppModelOption { slug: string; name: string; @@ -37,39 +29,6 @@ export interface AppModelOption { isCustom: boolean; } -const PROVIDER_CUSTOM_MODEL_CONFIG: Record = { - codex: { - provider: "codex", - title: "Codex", - description: "Save additional Codex model slugs for the picker and `/model` command.", - placeholder: "your-codex-model-slug", - example: "gpt-6.7-codex-ultra-preview", - }, - claudeAgent: { - provider: "claudeAgent", - title: "Claude", - description: "Save additional Claude model slugs for the picker and `/model` command.", - placeholder: "your-claude-model-slug", - example: "claude-sonnet-5-0", - }, - cursor: { - provider: "cursor", - title: "Cursor", - description: "Save additional Cursor model slugs for the picker and `/model` command.", - placeholder: "your-cursor-model-slug", - example: "claude-sonnet-4-6", - }, - opencode: { - provider: "opencode", - title: "OpenCode", - description: "Save additional OpenCode model slugs in `provider/model` format.", - placeholder: "openai/gpt-5", - example: "anthropic/claude-sonnet-4-5-20250929", - }, -}; - -export const MODEL_PROVIDER_SETTINGS = Object.values(PROVIDER_CUSTOM_MODEL_CONFIG); - export function normalizeCustomModelSlugs( models: Iterable, builtInModelSlugs: ReadonlySet, @@ -222,9 +181,7 @@ export function resolveAppModelSelectionState( model, models: getProviderModels(providers, provider), prompt: "", - modelOptions: { - [provider]: provider === selection.provider ? selection.options : undefined, - }, + modelOptions: provider === selection.provider ? selection.options : undefined, }); return createModelSelection(provider, model, modelOptionsForDispatch); diff --git a/apps/web/src/providerModels.ts b/apps/web/src/providerModels.ts index e901a895f49..229d61ca568 100644 --- a/apps/web/src/providerModels.ts +++ b/apps/web/src/providerModels.ts @@ -1,25 +1,23 @@ import { DEFAULT_MODEL_BY_PROVIDER, - type CursorModelOptions, type ModelCapabilities, type ProviderKind, type ServerProvider, type ServerProviderModel, } from "@t3tools/contracts"; -import { - hasEffortLevel, - normalizeModelSlug, - resolveContextWindow, - trimOrNull, -} from "@t3tools/shared/model"; +import { createModelCapabilities, normalizeModelSlug } from "@t3tools/shared/model"; + +const EMPTY_CAPABILITIES: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [], +}); -const EMPTY_CAPABILITIES: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -}; +export function formatProviderKindLabel(provider: ProviderKind): string { + return provider + .replace(/([a-z])([A-Z])/g, "$1 $2") + .replace(/[_-]+/g, " ") + .trim() + .replace(/\b\w/g, (char) => char.toUpperCase()); +} export function getProviderModels( providers: ReadonlyArray, @@ -35,6 +33,21 @@ export function getProviderSnapshot( return providers.find((candidate) => candidate.provider === provider); } +export function getProviderDisplayName( + providers: ReadonlyArray, + provider: ProviderKind, +): string { + const snapshot = getProviderSnapshot(providers, provider); + return snapshot?.displayName?.trim() || formatProviderKindLabel(provider); +} + +export function getProviderInteractionModeToggle( + providers: ReadonlyArray, + provider: ProviderKind, +): boolean { + return getProviderSnapshot(providers, provider)?.showInteractionModeToggle ?? true; +} + export function isProviderEnabled( providers: ReadonlyArray, provider: ProviderKind, @@ -76,30 +89,3 @@ export function getDefaultServerModel( DEFAULT_MODEL_BY_PROVIDER[provider] ); } - -export function normalizeCursorModelOptionsWithCapabilities( - caps: ModelCapabilities, - modelOptions: CursorModelOptions | null | undefined, -): CursorModelOptions | undefined { - const reasoning = trimOrNull(modelOptions?.reasoning); - const reasoningValue = - reasoning && hasEffortLevel(caps, reasoning) - ? (reasoning as CursorModelOptions["reasoning"]) - : undefined; - const fastMode = - caps.supportsFastMode && typeof modelOptions?.fastMode === "boolean" - ? modelOptions.fastMode - : undefined; - const thinking = - caps.supportsThinkingToggle && typeof modelOptions?.thinking === "boolean" - ? modelOptions.thinking - : undefined; - const contextWindow = resolveContextWindow(caps, modelOptions?.contextWindow); - const nextOptions: CursorModelOptions = { - ...(reasoningValue ? { reasoning: reasoningValue } : {}), - ...(fastMode !== undefined ? { fastMode } : {}), - ...(thinking !== undefined ? { thinking } : {}), - ...(contextWindow ? { contextWindow } : {}), - }; - return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; -} diff --git a/packages/contracts/src/model.ts b/packages/contracts/src/model.ts index 5bb82caf421..92de00bea3b 100644 --- a/packages/contracts/src/model.ts +++ b/packages/contracts/src/model.ts @@ -1,87 +1,127 @@ -import { Schema } from "effect"; +import { Effect, Schema, SchemaTransformation } from "effect"; import { TrimmedNonEmptyString } from "./baseSchemas.ts"; import type { ProviderKind } from "./orchestration.ts"; -export const CODEX_REASONING_EFFORT_OPTIONS = ["xhigh", "high", "medium", "low"] as const; -export const CodexReasoningEffort = Schema.Literals(CODEX_REASONING_EFFORT_OPTIONS); -export type CodexReasoningEffort = typeof CodexReasoningEffort.Type; -export const CLAUDE_AGENT_EFFORT_OPTIONS = [ - "low", - "medium", - "high", - "xhigh", - "max", - "ultrathink", -] as const; -export const ClaudeAgentEffort = Schema.Literals(CLAUDE_AGENT_EFFORT_OPTIONS); -export type ClaudeAgentEffort = typeof ClaudeAgentEffort.Type; -export type ClaudeCodeEffort = ClaudeAgentEffort; -export const CURSOR_REASONING_OPTIONS = ["low", "medium", "high", "max", "xhigh"] as const; -export const CursorReasoningOption = Schema.Literals(CURSOR_REASONING_OPTIONS); -export type CursorReasoningOption = typeof CursorReasoningOption.Type; - -export type ProviderReasoningEffort = - | CodexReasoningEffort - | ClaudeAgentEffort - | CursorReasoningOption; - -export const CodexModelOptions = Schema.Struct({ - reasoningEffort: Schema.optional(CodexReasoningEffort), - fastMode: Schema.optional(Schema.Boolean), -}); -export type CodexModelOptions = typeof CodexModelOptions.Type; +export const ProviderOptionDescriptorType = Schema.Literals(["select", "boolean"]); +export type ProviderOptionDescriptorType = typeof ProviderOptionDescriptorType.Type; -export const ClaudeModelOptions = Schema.Struct({ - thinking: Schema.optional(Schema.Boolean), - effort: Schema.optional(ClaudeAgentEffort), - fastMode: Schema.optional(Schema.Boolean), - contextWindow: Schema.optional(Schema.String), +export const ProviderOptionChoice = Schema.Struct({ + id: TrimmedNonEmptyString, + label: TrimmedNonEmptyString, + description: Schema.optional(TrimmedNonEmptyString), + isDefault: Schema.optional(Schema.Boolean), }); -export type ClaudeModelOptions = typeof ClaudeModelOptions.Type; +export type ProviderOptionChoice = typeof ProviderOptionChoice.Type; -export const CursorModelOptions = Schema.Struct({ - reasoning: Schema.optional(CursorReasoningOption), - fastMode: Schema.optional(Schema.Boolean), - thinking: Schema.optional(Schema.Boolean), - contextWindow: Schema.optional(Schema.String), -}); -export type CursorModelOptions = typeof CursorModelOptions.Type; -export const OpenCodeModelOptions = Schema.Struct({ - variant: Schema.optional(TrimmedNonEmptyString), - agent: Schema.optional(TrimmedNonEmptyString), -}); -export type OpenCodeModelOptions = typeof OpenCodeModelOptions.Type; +const ProviderOptionDescriptorBase = { + id: TrimmedNonEmptyString, + label: TrimmedNonEmptyString, + description: Schema.optional(TrimmedNonEmptyString), +} as const; -export const ProviderModelOptions = Schema.Struct({ - codex: Schema.optional(CodexModelOptions), - claudeAgent: Schema.optional(ClaudeModelOptions), - cursor: Schema.optional(CursorModelOptions), - opencode: Schema.optional(OpenCodeModelOptions), +export const SelectProviderOptionDescriptor = Schema.Struct({ + ...ProviderOptionDescriptorBase, + type: Schema.Literal("select"), + options: Schema.Array(ProviderOptionChoice), + currentValue: Schema.optional(TrimmedNonEmptyString), + promptInjectedValues: Schema.optional(Schema.Array(TrimmedNonEmptyString)), }); -export type ProviderModelOptions = typeof ProviderModelOptions.Type; +export type SelectProviderOptionDescriptor = typeof SelectProviderOptionDescriptor.Type; -export const EffortOption = Schema.Struct({ - value: TrimmedNonEmptyString, - label: TrimmedNonEmptyString, - isDefault: Schema.optional(Schema.Boolean), +export const BooleanProviderOptionDescriptor = Schema.Struct({ + ...ProviderOptionDescriptorBase, + type: Schema.Literal("boolean"), + currentValue: Schema.optional(Schema.Boolean), }); -export type EffortOption = typeof EffortOption.Type; +export type BooleanProviderOptionDescriptor = typeof BooleanProviderOptionDescriptor.Type; -export const ContextWindowOption = Schema.Struct({ - value: TrimmedNonEmptyString, - label: TrimmedNonEmptyString, - isDefault: Schema.optional(Schema.Boolean), +export const ProviderOptionDescriptor = Schema.Union([ + SelectProviderOptionDescriptor, + BooleanProviderOptionDescriptor, +]); +export type ProviderOptionDescriptor = typeof ProviderOptionDescriptor.Type; + +export const ProviderOptionSelectionValue = Schema.Union([TrimmedNonEmptyString, Schema.Boolean]); +export type ProviderOptionSelectionValue = typeof ProviderOptionSelectionValue.Type; + +export const ProviderOptionSelection = Schema.Struct({ + id: TrimmedNonEmptyString, + value: ProviderOptionSelectionValue, }); -export type ContextWindowOption = typeof ContextWindowOption.Type; +export type ProviderOptionSelection = typeof ProviderOptionSelection.Type; + +/** + * Legacy on-disk shape for provider option selections, kept readable by the + * decoder so we can tolerate stored data written before the v3 array shape. + * + * Persisted historically as `{ effort: "max", fastMode: true, ... }` inside + * `modelSelection.options`. Migration 026 rewrites stored rows to the + * canonical array shape, but we still see the legacy form in: + * - `settings.json` files from older client builds, + * - SQLite databases that have not yet run migration 026, + * - any future regression that re-introduces the legacy shape. + */ +const LegacyProviderOptionSelectionsObject = Schema.Record(Schema.String, Schema.Unknown); + +const ProviderOptionSelectionsFromLegacyObject = LegacyProviderOptionSelectionsObject.pipe( + Schema.decodeTo( + Schema.Array(ProviderOptionSelection), + SchemaTransformation.transformOrFail({ + decode: (record) => Effect.succeed(coerceLegacyOptionsObjectToArray(record)), + encode: (selections) => Effect.succeed(canonicalSelectionsToLegacyObject(selections)), + }), + ), +); + +/** + * Schema for the `options` field of every `ModelSelection` variant. + * + * Accepts both: + * - the canonical array shape `Array<{ id, value }>` (preferred), and + * - the legacy object shape `Record` from + * pre-migration data. + * + * Always normalizes to the canonical array on decode and re-encodes as the + * canonical array, so any legacy storage gets cleaned up the next time the + * containing record is written back. + */ +export const ProviderOptionSelections = Schema.Union([ + Schema.Array(ProviderOptionSelection), + ProviderOptionSelectionsFromLegacyObject, +]); +export type ProviderOptionSelections = typeof ProviderOptionSelections.Type; + +function coerceLegacyOptionsObjectToArray( + record: Record, +): ReadonlyArray { + const entries: Array = []; + for (const [rawKey, rawValue] of Object.entries(record)) { + const id = typeof rawKey === "string" ? rawKey.trim() : ""; + if (!id) continue; + if (typeof rawValue === "string") { + const trimmed = rawValue.trim(); + if (trimmed) entries.push({ id, value: trimmed }); + } else if (typeof rawValue === "boolean") { + entries.push({ id, value: rawValue }); + } + // Drop anything else (numbers, null, nested objects/arrays) to match the + // permissive normalization performed by migration 026. + } + return entries; +} + +function canonicalSelectionsToLegacyObject( + selections: ReadonlyArray, +): Record { + const out: Record = {}; + for (const { id, value } of selections) { + out[id] = value; + } + return out; +} export const ModelCapabilities = Schema.Struct({ - reasoningEffortLevels: Schema.Array(EffortOption), - supportsFastMode: Schema.Boolean, - supportsThinkingToggle: Schema.Boolean, - contextWindowOptions: Schema.Array(ContextWindowOption), - promptInjectedEffortLevels: Schema.Array(TrimmedNonEmptyString), - variantOptions: Schema.optional(Schema.Array(EffortOption)), - agentOptions: Schema.optional(Schema.Array(EffortOption)), + optionDescriptors: Schema.optional(Schema.Array(ProviderOptionDescriptor)), }); export type ModelCapabilities = typeof ModelCapabilities.Type; diff --git a/packages/contracts/src/orchestration.test.ts b/packages/contracts/src/orchestration.test.ts index 223efd6d270..190e09aa631 100644 --- a/packages/contracts/src/orchestration.test.ts +++ b/packages/contracts/src/orchestration.test.ts @@ -33,6 +33,13 @@ const decodeThreadTurnStartRequestedPayload = Schema.decodeUnknownEffect( const decodeOrchestrationLatestTurn = Schema.decodeUnknownEffect(OrchestrationLatestTurn); const decodeOrchestrationProposedPlan = Schema.decodeUnknownEffect(OrchestrationProposedPlan); const decodeOrchestrationSession = Schema.decodeUnknownEffect(OrchestrationSession); + +function getOptionValue( + options: ReadonlyArray<{ id: string; value: unknown }> | undefined, + id: string, +): unknown { + return options?.find((option) => option.id === id)?.value; +} const decodeThreadCreatedPayload = Schema.decodeUnknownEffect(ThreadCreatedPayload); const decodeOrchestrationCommand = Schema.decodeUnknownEffect(OrchestrationCommand); const decodeOrchestrationEvent = Schema.decodeUnknownEffect(OrchestrationEvent); @@ -364,19 +371,97 @@ it.effect("accepts provider-scoped model options in thread.turn.start", () => modelSelection: { provider: "codex", model: "gpt-5.3-codex", + options: [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], + }, + createdAt: "2026-01-01T00:00:00.000Z", + }); + assert.strictEqual(parsed.modelSelection?.provider, "codex"); + assert.strictEqual(getOptionValue(parsed.modelSelection?.options, "reasoningEffort"), "high"); + assert.strictEqual(getOptionValue(parsed.modelSelection?.options, "fastMode"), true); + }), +); + +it.effect("normalizes legacy object-shaped modelSelection.options on decode", () => + Effect.gen(function* () { + const parsed = yield* decodeThreadCreatedPayload({ + threadId: "thread-1", + projectId: "project-1", + title: "Legacy options thread", + modelSelection: { + provider: "claudeAgent", + model: "claude-opus-4-6", options: { - reasoningEffort: "high", + effort: "max", fastMode: true, + // Falsy/garbage entries are dropped, matching migration 026. + emptyStr: " ", + nullish: null, + nested: { foo: 1 }, }, }, + branch: null, + worktreePath: null, createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", }); - assert.strictEqual(parsed.modelSelection?.provider, "codex"); - assert.strictEqual(parsed.modelSelection?.options?.reasoningEffort, "high"); - assert.strictEqual(parsed.modelSelection?.options?.fastMode, true); + + assert.strictEqual(parsed.modelSelection.provider, "claudeAgent"); + assert.deepStrictEqual(parsed.modelSelection.options, [ + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ]); + }), +); + +it.effect("normalizes legacy object-shaped defaultModelSelection.options on decode", () => + Effect.gen(function* () { + const parsed = yield* decodeProjectCreatedPayload({ + projectId: "project-1", + title: "Legacy default project", + workspaceRoot: "/tmp/legacy", + defaultModelSelection: { + provider: "codex", + model: "gpt-5.4", + options: { reasoningEffort: "low" }, + }, + scripts: [], + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }); + + assert.deepStrictEqual(parsed.defaultModelSelection?.options, [ + { id: "reasoningEffort", value: "low" }, + ]); }), ); +it.effect( + "normalizes legacy object-shaped options on decode and re-encodes as canonical array", + () => + Effect.gen(function* () { + const decoded = yield* decodeThreadCreatedPayload({ + threadId: "thread-1", + projectId: "project-1", + title: "Round trip thread", + modelSelection: { + provider: "codex", + model: "gpt-5.4", + options: { fastMode: true }, + }, + branch: null, + worktreePath: null, + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + }); + + const encoded = yield* Schema.encodeEffect(ThreadCreatedPayload)(decoded); + assert.deepStrictEqual(encoded.modelSelection.options, [{ id: "fastMode", value: true }]); + }), +); + it.effect("accepts a title seed in thread.turn.start", () => Effect.gen(function* () { const parsed = yield* decodeThreadTurnStartCommand({ diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index 087a6670901..c201d6c82a3 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -1,10 +1,5 @@ import { Effect, Option, Schema, SchemaIssue, Struct } from "effect"; -import { - ClaudeModelOptions, - CodexModelOptions, - CursorModelOptions, - OpenCodeModelOptions, -} from "./model.ts"; +import { ProviderOptionSelections } from "./model.ts"; import { RepositoryIdentity } from "./environment.ts"; import { ApprovalRequestId, @@ -51,27 +46,27 @@ export const DEFAULT_PROVIDER_KIND: ProviderKind = "codex"; export const CodexModelSelection = Schema.Struct({ provider: Schema.Literal("codex"), model: TrimmedNonEmptyString, - options: Schema.optionalKey(CodexModelOptions), + options: Schema.optionalKey(ProviderOptionSelections), }); export type CodexModelSelection = typeof CodexModelSelection.Type; export const ClaudeModelSelection = Schema.Struct({ provider: Schema.Literal("claudeAgent"), model: TrimmedNonEmptyString, - options: Schema.optionalKey(ClaudeModelOptions), + options: Schema.optionalKey(ProviderOptionSelections), }); export type ClaudeModelSelection = typeof ClaudeModelSelection.Type; export const CursorModelSelection = Schema.Struct({ provider: Schema.Literal("cursor"), model: TrimmedNonEmptyString, - options: Schema.optionalKey(CursorModelOptions), + options: Schema.optionalKey(ProviderOptionSelections), }); export type CursorModelSelection = typeof CursorModelSelection.Type; export const OpenCodeModelSelection = Schema.Struct({ provider: Schema.Literal("opencode"), model: TrimmedNonEmptyString, - options: Schema.optionalKey(OpenCodeModelOptions), + options: Schema.optionalKey(ProviderOptionSelections), }); export type OpenCodeModelSelection = typeof OpenCodeModelSelection.Type; diff --git a/packages/contracts/src/provider.test.ts b/packages/contracts/src/provider.test.ts index f91d0c89e0a..9bbc3ff10b6 100644 --- a/packages/contracts/src/provider.test.ts +++ b/packages/contracts/src/provider.test.ts @@ -6,6 +6,13 @@ import { ProviderSendTurnInput, ProviderSessionStartInput } from "./provider.ts" const decodeProviderSessionStartInput = Schema.decodeUnknownSync(ProviderSessionStartInput); const decodeProviderSendTurnInput = Schema.decodeUnknownSync(ProviderSendTurnInput); +function getOptionValue( + options: ReadonlyArray<{ id: string; value: unknown }> | undefined, + id: string, +): unknown { + return options?.find((option) => option.id === id)?.value; +} + describe("ProviderSessionStartInput", () => { it("accepts codex-compatible payloads", () => { const parsed = decodeProviderSessionStartInput({ @@ -15,10 +22,10 @@ describe("ProviderSessionStartInput", () => { modelSelection: { provider: "codex", model: "gpt-5.3-codex", - options: { - reasoningEffort: "high", - fastMode: true, - }, + options: [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], }, runtimeMode: "full-access", }); @@ -28,8 +35,8 @@ describe("ProviderSessionStartInput", () => { if (parsed.modelSelection?.provider !== "codex") { throw new Error("Expected codex modelSelection"); } - expect(parsed.modelSelection.options?.reasoningEffort).toBe("high"); - expect(parsed.modelSelection.options?.fastMode).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "reasoningEffort")).toBe("high"); + expect(getOptionValue(parsed.modelSelection.options, "fastMode")).toBe(true); }); it("rejects payloads without runtime mode", () => { @@ -49,11 +56,11 @@ describe("ProviderSessionStartInput", () => { modelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6", - options: { - thinking: true, - effort: "max", - fastMode: true, - }, + options: [ + { id: "thinking", value: true }, + { id: "effort", value: "max" }, + { id: "fastMode", value: true }, + ], }, runtimeMode: "full-access", }); @@ -63,9 +70,9 @@ describe("ProviderSessionStartInput", () => { if (parsed.modelSelection?.provider !== "claudeAgent") { throw new Error("Expected claude modelSelection"); } - expect(parsed.modelSelection.options?.thinking).toBe(true); - expect(parsed.modelSelection.options?.effort).toBe("max"); - expect(parsed.modelSelection.options?.fastMode).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "thinking")).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "effort")).toBe("max"); + expect(getOptionValue(parsed.modelSelection.options, "fastMode")).toBe(true); expect(parsed.runtimeMode).toBe("full-access"); }); @@ -78,14 +85,14 @@ describe("ProviderSessionStartInput", () => { modelSelection: { provider: "cursor", model: "composer-2", - options: { fastMode: true }, + options: [{ id: "fastMode", value: true }], }, }); expect(parsed.provider).toBe("cursor"); expect(parsed.modelSelection?.provider).toBe("cursor"); expect(parsed.modelSelection?.model).toBe("composer-2"); if (parsed.modelSelection?.provider === "cursor") { - expect(parsed.modelSelection.options?.fastMode).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "fastMode")).toBe(true); } }); }); @@ -97,10 +104,10 @@ describe("ProviderSendTurnInput", () => { modelSelection: { provider: "codex", model: "gpt-5.3-codex", - options: { - reasoningEffort: "xhigh", - fastMode: true, - }, + options: [ + { id: "reasoningEffort", value: "xhigh" }, + { id: "fastMode", value: true }, + ], }, }); @@ -109,8 +116,8 @@ describe("ProviderSendTurnInput", () => { if (parsed.modelSelection?.provider !== "codex") { throw new Error("Expected codex modelSelection"); } - expect(parsed.modelSelection.options?.reasoningEffort).toBe("xhigh"); - expect(parsed.modelSelection.options?.fastMode).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "reasoningEffort")).toBe("xhigh"); + expect(getOptionValue(parsed.modelSelection.options, "fastMode")).toBe(true); }); it("accepts claude modelSelection including ultrathink", () => { @@ -119,10 +126,10 @@ describe("ProviderSendTurnInput", () => { modelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6", - options: { - effort: "ultrathink", - fastMode: true, - }, + options: [ + { id: "effort", value: "ultrathink" }, + { id: "fastMode", value: true }, + ], }, }); @@ -130,7 +137,7 @@ describe("ProviderSendTurnInput", () => { if (parsed.modelSelection?.provider !== "claudeAgent") { throw new Error("Expected claude modelSelection"); } - expect(parsed.modelSelection.options?.effort).toBe("ultrathink"); - expect(parsed.modelSelection.options?.fastMode).toBe(true); + expect(getOptionValue(parsed.modelSelection.options, "effort")).toBe("ultrathink"); + expect(getOptionValue(parsed.modelSelection.options, "fastMode")).toBe(true); }); }); diff --git a/packages/contracts/src/server.ts b/packages/contracts/src/server.ts index 3cd25f2e8e9..1f048c13407 100644 --- a/packages/contracts/src/server.ts +++ b/packages/contracts/src/server.ts @@ -85,6 +85,9 @@ export type ServerProviderSkill = typeof ServerProviderSkill.Type; export const ServerProvider = Schema.Struct({ provider: ProviderKind, + displayName: Schema.optional(TrimmedNonEmptyString), + badgeLabel: Schema.optional(TrimmedNonEmptyString), + showInteractionModeToggle: Schema.optional(Schema.Boolean), enabled: Schema.Boolean, installed: Schema.Boolean, version: Schema.NullOr(TrimmedNonEmptyString), diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index cad1d197c12..6301364f337 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -3,11 +3,8 @@ import * as Schema from "effect/Schema"; import * as SchemaTransformation from "effect/SchemaTransformation"; import { TrimmedNonEmptyString, TrimmedString } from "./baseSchemas.ts"; import { - ClaudeModelOptions, - CodexModelOptions, - CursorModelOptions, DEFAULT_GIT_TEXT_GENERATION_MODEL_BY_PROVIDER, - OpenCodeModelOptions, + ProviderOptionSelections, } from "./model.ts"; import { ModelSelection, ProviderKind } from "./orchestration.ts"; @@ -170,50 +167,26 @@ export const DEFAULT_UNIFIED_SETTINGS: UnifiedSettings = { // ── Server Settings Patch (replace with a Schema.deepPartial if available) ────────────────────────────────────────── -const CodexModelOptionsPatch = Schema.Struct({ - reasoningEffort: Schema.optionalKey(CodexModelOptions.fields.reasoningEffort), - fastMode: Schema.optionalKey(CodexModelOptions.fields.fastMode), -}); - -const ClaudeModelOptionsPatch = Schema.Struct({ - thinking: Schema.optionalKey(ClaudeModelOptions.fields.thinking), - effort: Schema.optionalKey(ClaudeModelOptions.fields.effort), - fastMode: Schema.optionalKey(ClaudeModelOptions.fields.fastMode), - contextWindow: Schema.optionalKey(ClaudeModelOptions.fields.contextWindow), -}); - -const CursorModelOptionsPatch = Schema.Struct({ - reasoning: Schema.optionalKey(CursorModelOptions.fields.reasoning), - fastMode: Schema.optionalKey(CursorModelOptions.fields.fastMode), - thinking: Schema.optionalKey(CursorModelOptions.fields.thinking), - contextWindow: Schema.optionalKey(CursorModelOptions.fields.contextWindow), -}); - -const OpenCodeModelOptionsPatch = Schema.Struct({ - variant: Schema.optionalKey(OpenCodeModelOptions.fields.variant), - agent: Schema.optionalKey(OpenCodeModelOptions.fields.agent), -}); - const ModelSelectionPatch = Schema.Union([ Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("codex")), model: Schema.optionalKey(TrimmedNonEmptyString), - options: Schema.optionalKey(CodexModelOptionsPatch), + options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("claudeAgent")), model: Schema.optionalKey(TrimmedNonEmptyString), - options: Schema.optionalKey(ClaudeModelOptionsPatch), + options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("cursor")), model: Schema.optionalKey(TrimmedNonEmptyString), - options: Schema.optionalKey(CursorModelOptionsPatch), + options: Schema.optionalKey(ProviderOptionSelections), }), Schema.Struct({ provider: Schema.optionalKey(Schema.Literal("opencode")), model: Schema.optionalKey(TrimmedNonEmptyString), - options: Schema.optionalKey(OpenCodeModelOptionsPatch), + options: Schema.optionalKey(ProviderOptionSelections), }), ]); diff --git a/packages/shared/src/model.test.ts b/packages/shared/src/model.test.ts index 426ceca865e..242e7982234 100644 --- a/packages/shared/src/model.test.ts +++ b/packages/shared/src/model.test.ts @@ -3,46 +3,67 @@ import { DEFAULT_MODEL_BY_PROVIDER, type ModelCapabilities } from "@t3tools/cont import { applyClaudePromptEffortPrefix, - getDefaultContextWindow, - getDefaultEffort, - hasContextWindowOption, - hasEffortLevel, + buildProviderOptionSelectionsFromDescriptors, + createModelCapabilities, + createModelSelection, + getModelSelectionBooleanOptionValue, + getModelSelectionStringOptionValue, + getProviderOptionDescriptors, + getProviderOptionBooleanSelectionValue, + getProviderOptionStringSelectionValue, isClaudeUltrathinkPrompt, - normalizeClaudeModelOptionsWithCapabilities, - normalizeCodexModelOptionsWithCapabilities, normalizeModelSlug, - resolveContextWindow, - resolveEffort, resolveModelSlugForProvider, resolveSelectableModel, trimOrNull, } from "./model.ts"; -const codexCaps: ModelCapabilities = { - reasoningEffortLevels: [ - { value: "xhigh", label: "Extra High" }, - { value: "high", label: "High", isDefault: true }, +const codexCaps: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [ + { + id: "reasoningEffort", + label: "Reasoning", + type: "select", + options: [ + { id: "xhigh", label: "Extra High" }, + { id: "high", label: "High", isDefault: true }, + ], + currentValue: "high", + }, + { + id: "fastMode", + label: "Fast Mode", + type: "boolean", + }, ], - supportsFastMode: true, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], -}; - -const claudeCaps: ModelCapabilities = { - reasoningEffortLevels: [ - { value: "medium", label: "Medium" }, - { value: "high", label: "High", isDefault: true }, - { value: "ultrathink", label: "Ultrathink" }, - ], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [ - { value: "200k", label: "200k" }, - { value: "1m", label: "1M", isDefault: true }, +}); + +const claudeCaps: ModelCapabilities = createModelCapabilities({ + optionDescriptors: [ + { + id: "effort", + label: "Reasoning", + type: "select", + options: [ + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + { id: "ultrathink", label: "Ultrathink" }, + ], + currentValue: "high", + promptInjectedValues: ["ultrathink"], + }, + { + id: "contextWindow", + label: "Context Window", + type: "select", + options: [ + { id: "200k", label: "200k" }, + { id: "1m", label: "1M", isDefault: true }, + ], + currentValue: "1m", + }, ], - promptInjectedEffortLevels: ["ultrathink"], -}; +}); describe("normalizeModelSlug", () => { it("maps known aliases to canonical slugs", () => { @@ -86,54 +107,6 @@ describe("resolveSelectableModel", () => { }); }); -describe("capability helpers", () => { - it("reads default efforts", () => { - expect(getDefaultEffort(codexCaps)).toBe("high"); - expect(getDefaultEffort(claudeCaps)).toBe("high"); - }); - - it("checks effort support", () => { - expect(hasEffortLevel(codexCaps, "xhigh")).toBe(true); - expect(hasEffortLevel(codexCaps, "max")).toBe(false); - }); -}); - -describe("resolveEffort", () => { - it("returns the explicit value when supported and not prompt-injected", () => { - expect(resolveEffort(codexCaps, "xhigh")).toBe("xhigh"); - expect(resolveEffort(codexCaps, "high")).toBe("high"); - expect(resolveEffort(claudeCaps, "medium")).toBe("medium"); - }); - - it("falls back to default when value is unsupported", () => { - expect(resolveEffort(codexCaps, "bogus")).toBe("high"); - expect(resolveEffort(claudeCaps, "bogus")).toBe("high"); - }); - - it("returns the default when no value is provided", () => { - expect(resolveEffort(codexCaps, undefined)).toBe("high"); - expect(resolveEffort(codexCaps, null)).toBe("high"); - expect(resolveEffort(codexCaps, "")).toBe("high"); - expect(resolveEffort(codexCaps, " ")).toBe("high"); - }); - - it("excludes prompt-injected efforts and falls back to default", () => { - expect(resolveEffort(claudeCaps, "ultrathink")).toBe("high"); - }); - - it("returns undefined for models with no effort levels", () => { - const noCaps: ModelCapabilities = { - reasoningEffortLevels: [], - supportsFastMode: false, - supportsThinkingToggle: false, - contextWindowOptions: [], - promptInjectedEffortLevels: [], - }; - expect(resolveEffort(noCaps, undefined)).toBeUndefined(); - expect(resolveEffort(noCaps, "high")).toBeUndefined(); - }); -}); - describe("misc helpers", () => { it("detects ultrathink prompts", () => { expect(isClaudeUltrathinkPrompt("Please ultrathink about this")).toBe(true); @@ -156,95 +129,88 @@ describe("misc helpers", () => { }); }); -describe("context window helpers", () => { - it("reads default context window", () => { - expect(getDefaultContextWindow(claudeCaps)).toBe("1m"); - }); - - it("returns null for models without context window options", () => { - expect(getDefaultContextWindow(codexCaps)).toBeNull(); - }); - - it("checks context window support", () => { - expect(hasContextWindowOption(claudeCaps, "1m")).toBe(true); - expect(hasContextWindowOption(claudeCaps, "200k")).toBe(true); - expect(hasContextWindowOption(claudeCaps, "bogus")).toBe(false); - expect(hasContextWindowOption(codexCaps, "1m")).toBe(false); - }); -}); - -describe("resolveContextWindow", () => { - it("returns the explicit value when supported", () => { - expect(resolveContextWindow(claudeCaps, "200k")).toBe("200k"); - expect(resolveContextWindow(claudeCaps, "1m")).toBe("1m"); - }); - - it("falls back to default when value is unsupported", () => { - expect(resolveContextWindow(claudeCaps, "bogus")).toBe("1m"); - }); - - it("returns the default when no value is provided", () => { - expect(resolveContextWindow(claudeCaps, undefined)).toBe("1m"); - expect(resolveContextWindow(claudeCaps, null)).toBe("1m"); - expect(resolveContextWindow(claudeCaps, "")).toBe("1m"); - }); - - it("returns undefined for models with no context window options", () => { - expect(resolveContextWindow(codexCaps, undefined)).toBeUndefined(); - expect(resolveContextWindow(codexCaps, "1m")).toBeUndefined(); - }); -}); - -describe("normalize*ModelOptionsWithCapabilities", () => { - it("preserves explicit false codex fast mode", () => { +describe("descriptor helpers", () => { + it("applies selection values to capability descriptors", () => { expect( - normalizeCodexModelOptionsWithCapabilities(codexCaps, { - reasoningEffort: "high", - fastMode: false, + getProviderOptionDescriptors({ + caps: claudeCaps, + selections: [ + { id: "effort", value: "medium" }, + { id: "contextWindow", value: "200k" }, + ], }), - ).toEqual({ - reasoningEffort: "high", - fastMode: false, + ).toEqual([ + { + id: "effort", + label: "Reasoning", + type: "select", + options: [ + { id: "medium", label: "Medium" }, + { id: "high", label: "High", isDefault: true }, + { id: "ultrathink", label: "Ultrathink" }, + ], + currentValue: "medium", + promptInjectedValues: ["ultrathink"], + }, + { + id: "contextWindow", + label: "Context Window", + type: "select", + options: [ + { id: "200k", label: "200k" }, + { id: "1m", label: "1M", isDefault: true }, + ], + currentValue: "200k", + }, + ]); + }); + + it("builds wire-format option selections from descriptors", () => { + const descriptors = getProviderOptionDescriptors({ + caps: codexCaps, + selections: [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], }); + + expect(buildProviderOptionSelectionsFromDescriptors(descriptors)).toEqual([ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]); }); - it("preserves the default Claude context window explicitly", () => { + it("stores option selection arrays in model selections", () => { expect( - normalizeClaudeModelOptionsWithCapabilities( - { - ...claudeCaps, - contextWindowOptions: [ - { value: "200k", label: "200k", isDefault: true }, - { value: "1m", label: "1M" }, - ], - }, - { - effort: "high", - contextWindow: "200k", - }, - ), + createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), ).toEqual({ - effort: "high", - contextWindow: "200k", + provider: "codex", + model: "gpt-5.4", + options: [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ], }); }); - it("omits unsupported Claude context window options", () => { + it("reads typed option selection values", () => { + const selection = createModelSelection("codex", "gpt-5.4", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]); + + expect(getProviderOptionStringSelectionValue(selection.options, "reasoningEffort")).toBe( + "high", + ); + expect(getProviderOptionStringSelectionValue(selection.options, "fastMode")).toBeUndefined(); + expect(getProviderOptionBooleanSelectionValue(selection.options, "fastMode")).toBe(true); expect( - normalizeClaudeModelOptionsWithCapabilities( - { - ...claudeCaps, - reasoningEffortLevels: [], - supportsThinkingToggle: true, - contextWindowOptions: [], - }, - { - thinking: true, - contextWindow: "1m", - }, - ), - ).toEqual({ - thinking: true, - }); + getProviderOptionBooleanSelectionValue(selection.options, "reasoningEffort"), + ).toBeUndefined(); + expect(getModelSelectionStringOptionValue(selection, "reasoningEffort")).toBe("high"); + expect(getModelSelectionBooleanOptionValue(selection, "fastMode")).toBe(true); }); }); diff --git a/packages/shared/src/model.ts b/packages/shared/src/model.ts index ad62debf68c..4f61fc33e83 100644 --- a/packages/shared/src/model.ts +++ b/packages/shared/src/model.ts @@ -1,15 +1,11 @@ import { DEFAULT_MODEL_BY_PROVIDER, MODEL_SLUG_ALIASES_BY_PROVIDER, - type ClaudeAgentEffort, - type ClaudeModelOptions, - type CodexModelOptions, - type CursorModelOptions, type ModelCapabilities, type ModelSelection, - type OpenCodeModelOptions, + type ProviderOptionDescriptor, + type ProviderOptionSelection, type ProviderKind, - type ProviderModelOptions, } from "@t3tools/contracts"; export interface SelectableModelOption { @@ -17,157 +13,215 @@ export interface SelectableModelOption { name: string; } -/** Check whether a capabilities object includes a given effort value. */ -export function hasEffortLevel(caps: ModelCapabilities, value: string): boolean { - return caps.reasoningEffortLevels.some((l) => l.value === value); +export function createModelCapabilities(input: { + optionDescriptors: ReadonlyArray; +}): ModelCapabilities { + return { + optionDescriptors: input.optionDescriptors.map(cloneDescriptor), + }; } -/** Return the default effort value for a capabilities object, or null if none. */ -export function getDefaultEffort(caps: ModelCapabilities): string | null { - return caps.reasoningEffortLevels.find((l) => l.isDefault)?.value ?? null; +function getRawSelectionValueById( + selections: ReadonlyArray | null | undefined, + id: string, +): string | boolean | undefined { + const selection = selections?.find((candidate) => candidate.id === id); + return selection?.value; } -/** - * Resolve a raw effort option against capabilities. - * - * Returns the explicit supported value when present and not prompt-injected, - * otherwise the model default. Returns `undefined` when the model exposes no - * effort levels. - */ -export function resolveEffort( - caps: ModelCapabilities, +export function getProviderOptionSelectionValue( + selections: ReadonlyArray | null | undefined, + id: string, +): string | boolean | undefined { + return getRawSelectionValueById(selections, id); +} + +export function getProviderOptionStringSelectionValue( + selections: ReadonlyArray | null | undefined, + id: string, +): string | undefined { + const value = getProviderOptionSelectionValue(selections, id); + return typeof value === "string" ? value : undefined; +} + +export function getProviderOptionBooleanSelectionValue( + selections: ReadonlyArray | null | undefined, + id: string, +): boolean | undefined { + const value = getProviderOptionSelectionValue(selections, id); + return typeof value === "boolean" ? value : undefined; +} + +export function getModelSelectionOptionValue( + modelSelection: ModelSelection | null | undefined, + id: string, +): string | boolean | undefined { + return getProviderOptionSelectionValue(modelSelection?.options, id); +} + +export function getModelSelectionStringOptionValue( + modelSelection: ModelSelection | null | undefined, + id: string, +): string | undefined { + return getProviderOptionStringSelectionValue(modelSelection?.options, id); +} + +export function getModelSelectionBooleanOptionValue( + modelSelection: ModelSelection | null | undefined, + id: string, +): boolean | undefined { + return getProviderOptionBooleanSelectionValue(modelSelection?.options, id); +} + +function resolveDescriptorChoiceValue( + descriptor: Extract, raw: string | null | undefined, ): string | undefined { - const defaultValue = getDefaultEffort(caps); - const trimmed = typeof raw === "string" ? raw.trim() : null; + const trimmed = trimOrNull(raw); + if (!trimmed) { + return descriptor.currentValue ?? descriptor.options.find((option) => option.isDefault)?.id; + } + if (descriptor.options.length === 0) { + return trimmed; + } if ( - trimmed && - !caps.promptInjectedEffortLevels.includes(trimmed) && - hasEffortLevel(caps, trimmed) + descriptor.promptInjectedValues?.includes(trimmed) && + descriptor.options.some((option) => option.id === trimmed) ) { + return descriptor.options.find((option) => option.isDefault)?.id; + } + if (descriptor.options.some((option) => option.id === trimmed)) { return trimmed; } - return defaultValue ?? undefined; + return descriptor.currentValue ?? descriptor.options.find((option) => option.isDefault)?.id; } -/** Check whether a capabilities object includes a given context window value. */ -export function hasContextWindowOption(caps: ModelCapabilities, value: string): boolean { - return caps.contextWindowOptions.some((o) => o.value === value); +function cloneDescriptor(descriptor: ProviderOptionDescriptor): ProviderOptionDescriptor { + return descriptor.type === "select" + ? { + ...descriptor, + options: [...descriptor.options], + ...(descriptor.promptInjectedValues + ? { promptInjectedValues: [...descriptor.promptInjectedValues] } + : {}), + } + : { ...descriptor }; } -/** Return the default context window value, or `null` if none is defined. */ -export function getDefaultContextWindow(caps: ModelCapabilities): string | null { - return caps.contextWindowOptions.find((o) => o.isDefault)?.value ?? null; -} - -/** - * Resolve a raw `contextWindow` option against capabilities. - * - * Returns the explicit supported value when present, otherwise the model - * default. Returns `undefined` when the model exposes no context window options. - */ -export function resolveContextWindow( - caps: ModelCapabilities, - raw: string | null | undefined, -): string | undefined { - const defaultValue = getDefaultContextWindow(caps); - if (!raw) return defaultValue ?? undefined; - return hasContextWindowOption(caps, raw) ? raw : (defaultValue ?? undefined); +function cloneSelection(selection: ProviderOptionSelection): ProviderOptionSelection { + return { ...selection }; } -export function normalizeCodexModelOptionsWithCapabilities( - caps: ModelCapabilities, - modelOptions: CodexModelOptions | null | undefined, -): CodexModelOptions | undefined { - const reasoningEffort = resolveEffort(caps, modelOptions?.reasoningEffort); - const fastMode = caps.supportsFastMode ? modelOptions?.fastMode : undefined; - const nextOptions: CodexModelOptions = { - ...(reasoningEffort - ? { reasoningEffort: reasoningEffort as CodexModelOptions["reasoningEffort"] } - : {}), - ...(fastMode !== undefined ? { fastMode } : {}), +function withDescriptorCurrentValue( + descriptor: ProviderOptionDescriptor, + rawCurrentValue: string | boolean | undefined, +): ProviderOptionDescriptor { + if (descriptor.type === "boolean") { + if (typeof rawCurrentValue === "boolean") { + return { + ...descriptor, + currentValue: rawCurrentValue, + }; + } + return descriptor; + } + const currentValue = + typeof rawCurrentValue === "string" + ? resolveDescriptorChoiceValue(descriptor, rawCurrentValue) + : resolveDescriptorChoiceValue(descriptor, descriptor.currentValue); + if (!currentValue) { + const { currentValue: _unusedCurrentValue, ...rest } = descriptor; + return rest; + } + return { + ...descriptor, + currentValue, }; - return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; } -export function normalizeClaudeModelOptionsWithCapabilities( - caps: ModelCapabilities, - modelOptions: ClaudeModelOptions | null | undefined, -): ClaudeModelOptions | undefined { - const effort = resolveEffort(caps, modelOptions?.effort); - const thinking = caps.supportsThinkingToggle ? modelOptions?.thinking : undefined; - const fastMode = caps.supportsFastMode ? modelOptions?.fastMode : undefined; - const contextWindow = resolveContextWindow(caps, modelOptions?.contextWindow); - const nextOptions: ClaudeModelOptions = { - ...(thinking !== undefined ? { thinking } : {}), - ...(effort ? { effort: effort as ClaudeModelOptions["effort"] } : {}), - ...(fastMode !== undefined ? { fastMode } : {}), - ...(contextWindow !== undefined ? { contextWindow } : {}), - }; - return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; +export function getProviderOptionDescriptors(input: { + caps: ModelCapabilities; + selections?: ReadonlyArray | null | undefined; +}): ReadonlyArray { + const { caps, selections } = input; + const baseDescriptors = (caps.optionDescriptors ?? []).map(cloneDescriptor); + + return baseDescriptors.map((descriptor) => + withDescriptorCurrentValue( + descriptor, + getRawSelectionValueById(selections, descriptor.id) ?? descriptor.currentValue, + ), + ); } -export function normalizeCursorModelOptionsWithCapabilities( - caps: ModelCapabilities, - modelOptions: CursorModelOptions | null | undefined, -): CursorModelOptions | undefined { - const reasoning = resolveEffort(caps, modelOptions?.reasoning); - const thinking = caps.supportsThinkingToggle ? modelOptions?.thinking : undefined; - const fastMode = caps.supportsFastMode ? modelOptions?.fastMode : undefined; - const contextWindow = resolveContextWindow(caps, modelOptions?.contextWindow); - const nextOptions: CursorModelOptions = { - ...(reasoning ? { reasoning: reasoning as CursorModelOptions["reasoning"] } : {}), - ...(fastMode !== undefined ? { fastMode } : {}), - ...(thinking !== undefined ? { thinking } : {}), - ...(contextWindow !== undefined ? { contextWindow } : {}), - }; - return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; +export function getProviderOptionCurrentValue( + descriptor: ProviderOptionDescriptor | null | undefined, +): string | boolean | undefined { + if (!descriptor) { + return undefined; + } + if (descriptor.type === "boolean") { + return descriptor.currentValue; + } + if (descriptor.currentValue) { + return descriptor.currentValue; + } + return descriptor.options.find((option) => option.isDefault)?.id; } -function resolveLabeledOption( - options: ReadonlyArray<{ value: string; isDefault?: boolean | undefined }> | undefined, - raw: string | null | undefined, +export function getProviderOptionCurrentLabel( + descriptor: ProviderOptionDescriptor | null | undefined, ): string | undefined { - if (!options || options.length === 0) { - return raw ?? undefined; + if (!descriptor) { + return undefined; + } + if (descriptor.type === "boolean") { + return typeof descriptor.currentValue === "boolean" + ? descriptor.currentValue + ? "On" + : "Off" + : undefined; } - if (raw && options.some((option) => option.value === raw)) { - return raw; + const currentValue = getProviderOptionCurrentValue(descriptor); + if (typeof currentValue !== "string") { + return undefined; } - return options.find((option) => option.isDefault)?.value ?? options[0]?.value; + return descriptor.options.find((option) => option.id === currentValue)?.label; } -export function normalizeOpenCodeModelOptionsWithCapabilities( - caps: ModelCapabilities, - modelOptions: OpenCodeModelOptions | null | undefined, -): OpenCodeModelOptions | undefined { - const variant = resolveLabeledOption(caps.variantOptions, trimOrNull(modelOptions?.variant)); - const agent = resolveLabeledOption(caps.agentOptions, trimOrNull(modelOptions?.agent)); - const nextOptions: OpenCodeModelOptions = { - ...(variant ? { variant } : {}), - ...(agent ? { agent } : {}), - }; - return Object.keys(nextOptions).length > 0 ? nextOptions : undefined; +export function buildProviderOptionSelectionsFromDescriptors( + descriptors: ReadonlyArray | null | undefined, +): Array | undefined { + if (!descriptors || descriptors.length === 0) { + return undefined; + } + + const nextSelections: Array = []; + + for (const descriptor of descriptors) { + const value = getProviderOptionCurrentValue(descriptor); + if (typeof value === "string" || typeof value === "boolean") { + nextSelections.push({ id: descriptor.id, value }); + } + } + + return nextSelections.length > 0 ? nextSelections : undefined; } -export function normalizeProviderModelOptionsWithCapabilities( - provider: ProviderKind, - caps: ModelCapabilities, - modelOptions: ProviderModelOptions[ProviderKind] | null | undefined, -): ProviderModelOptions[ProviderKind] | undefined { - switch (provider) { - case "codex": - return normalizeCodexModelOptionsWithCapabilities(caps, modelOptions as CodexModelOptions); - case "claudeAgent": - return normalizeClaudeModelOptionsWithCapabilities(caps, modelOptions as ClaudeModelOptions); - case "cursor": - return normalizeCursorModelOptionsWithCapabilities(caps, modelOptions as CursorModelOptions); - case "opencode": - return normalizeOpenCodeModelOptionsWithCapabilities( - caps, - modelOptions as OpenCodeModelOptions, - ); +export function getModelSelectionOptionDescriptors( + modelSelection: ModelSelection | null | undefined, + caps?: ModelCapabilities | null | undefined, +): ReadonlyArray { + if (!modelSelection) { + return []; } + if (!caps) { + return []; + } + return getProviderOptionDescriptors({ + caps, + selections: modelSelection.options, + }); } export function isClaudeUltrathinkPrompt(text: string | null | undefined): boolean { @@ -249,42 +303,51 @@ export function trimOrNull(value: T | null | undefined): T | n return trimmed || null; } +function cloneSelections( + selections: ReadonlyArray, +): Array { + return selections.map(cloneSelection); +} + export function createModelSelection( provider: ProviderKind, model: string, - options?: ProviderModelOptions[ProviderKind] | undefined, + options?: ReadonlyArray | null, ): ModelSelection { - switch (provider) { - case "codex": - return { - provider, - model, - ...(options ? { options: options as CodexModelOptions } : {}), - }; - case "claudeAgent": - return { - provider, - model, - ...(options ? { options: options as ClaudeModelOptions } : {}), - }; - case "cursor": - return { - provider, - model, - ...(options ? { options: options as CursorModelOptions } : {}), - }; - case "opencode": - return { - provider, - model, - ...(options ? { options: options as OpenCodeModelOptions } : {}), - }; + const selections = options ? cloneSelections(options) : []; + return { + provider, + model, + ...(selections.length > 0 ? { options: selections } : {}), + } as ModelSelection; +} + +/** + * Returns the effort value if it is a prompt-injected value according to + * any select descriptor in the given capabilities, or null otherwise. + * + * Unlike a single `find`, this checks every descriptor so that the + * correct descriptor's `promptInjectedValues` list is consulted even when + * multiple select descriptors exist. + */ +export function resolvePromptInjectedEffort( + caps: ModelCapabilities, + rawEffort: string | null | undefined, +): string | null { + const trimmed = trimOrNull(rawEffort); + if (!trimmed) return null; + const descriptors = getProviderOptionDescriptors({ caps }); + for (const descriptor of descriptors) { + if (descriptor.type === "select" && descriptor.promptInjectedValues?.includes(trimmed)) { + return trimmed; + } } + return null; } export function applyClaudePromptEffortPrefix( text: string, - effort: ClaudeAgentEffort | null | undefined, + effort: string | null | undefined, ): string { const trimmed = text.trim(); if (!trimmed) { diff --git a/packages/shared/src/serverSettings.test.ts b/packages/shared/src/serverSettings.test.ts index bbe5d8dc2af..ccae816cf5c 100644 --- a/packages/shared/src/serverSettings.test.ts +++ b/packages/shared/src/serverSettings.test.ts @@ -1,5 +1,6 @@ import { DEFAULT_SERVER_SETTINGS } from "@t3tools/contracts"; import { describe, expect, it } from "vitest"; +import { createModelSelection } from "./model.ts"; import { applyServerSettingsPatch, extractPersistedServerObservabilitySettings, @@ -56,14 +57,10 @@ describe("serverSettings helpers", () => { it("replaces text generation selection when provider/model are provided", () => { const current = { ...DEFAULT_SERVER_SETTINGS, - textGenerationModelSelection: { - provider: "codex" as const, - model: "gpt-5.4-mini", - options: { - reasoningEffort: "high" as const, - fastMode: true, - }, - }, + textGenerationModelSelection: createModelSelection("codex", "gpt-5.4-mini", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), }; expect( @@ -82,45 +79,35 @@ describe("serverSettings helpers", () => { it("still deep merges text generation selection when only options are provided", () => { const current = { ...DEFAULT_SERVER_SETTINGS, - textGenerationModelSelection: { - provider: "codex" as const, - model: "gpt-5.4-mini", - options: { - reasoningEffort: "high" as const, - fastMode: true, - }, - }, + textGenerationModelSelection: createModelSelection("codex", "gpt-5.4-mini", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), }; expect( applyServerSettingsPatch(current, { textGenerationModelSelection: { - options: { - fastMode: false, - }, + options: [{ id: "fastMode", value: false }], }, }).textGenerationModelSelection, ).toEqual({ provider: "codex", model: "gpt-5.4-mini", - options: { - reasoningEffort: "high", - fastMode: false, - }, + options: [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: false }, + ], }); }); it("replaces text generation selection across providers without leaking stale options", () => { const current = { ...DEFAULT_SERVER_SETTINGS, - textGenerationModelSelection: { - provider: "codex" as const, - model: "gpt-5.4-mini", - options: { - reasoningEffort: "high" as const, - fastMode: true, - }, - }, + textGenerationModelSelection: createModelSelection("codex", "gpt-5.4-mini", [ + { id: "reasoningEffort", value: "high" }, + { id: "fastMode", value: true }, + ]), }; expect( @@ -135,4 +122,26 @@ describe("serverSettings helpers", () => { model: "openai/gpt-5", }); }); + + it("accepts array-based text generation selection patches", () => { + expect( + applyServerSettingsPatch(DEFAULT_SERVER_SETTINGS, { + textGenerationModelSelection: { + provider: "opencode", + model: "openai/gpt-5", + options: [ + { id: "variant", value: "prod" }, + { id: "agent", value: "build" }, + ], + }, + }).textGenerationModelSelection, + ).toEqual({ + provider: "opencode", + model: "openai/gpt-5", + options: [ + { id: "variant", value: "prod" }, + { id: "agent", value: "build" }, + ], + }); + }); }); diff --git a/packages/shared/src/serverSettings.ts b/packages/shared/src/serverSettings.ts index cb9d9373573..2488f602934 100644 --- a/packages/shared/src/serverSettings.ts +++ b/packages/shared/src/serverSettings.ts @@ -1,14 +1,8 @@ -import { - ServerSettings, - type ClaudeModelOptions, - type CodexModelOptions, - type CursorModelOptions, - type OpenCodeModelOptions, - type ServerSettingsPatch, -} from "@t3tools/contracts"; +import { ServerSettings, type ServerSettingsPatch } from "@t3tools/contracts"; import { Schema } from "effect"; import { deepMerge } from "./Struct.ts"; import { fromLenientJson } from "./schemaJson.ts"; +import { createModelSelection } from "./model.ts"; const ServerSettingsJson = fromLenientJson(ServerSettings); @@ -53,8 +47,23 @@ function shouldReplaceTextGenerationModelSelection( return Boolean(patch && (patch.provider !== undefined || patch.model !== undefined)); } -const withModelSelectionOptions = (options: Options | undefined) => - options ? { options } : {}; +function mergeModelSelectionOptionsById(input: { + current: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined; + patch: ReadonlyArray<{ readonly id: string; readonly value: string | boolean }> | undefined; +}): Array<{ id: string; value: string | boolean }> | undefined { + if (input.patch === undefined) { + return input.current ? [...input.current] : undefined; + } + if (input.patch.length === 0) { + return undefined; + } + + const merged = new Map((input.current ?? []).map((selection) => [selection.id, selection.value])); + for (const selection of input.patch) { + merged.set(selection.id, selection.value); + } + return [...merged.entries()].map(([id, value]) => ({ id, value })); +} /** * Applies a server settings patch while treating textGenerationModelSelection as @@ -67,44 +76,21 @@ export function applyServerSettingsPatch( ): ServerSettings { const selectionPatch = patch.textGenerationModelSelection; const next = deepMerge(current, patch); - if (!selectionPatch || !shouldReplaceTextGenerationModelSelection(selectionPatch)) { + if (!selectionPatch) { return next; } const provider = selectionPatch.provider ?? current.textGenerationModelSelection.provider; const model = selectionPatch.model ?? current.textGenerationModelSelection.model; + const options = shouldReplaceTextGenerationModelSelection(selectionPatch) + ? selectionPatch.options + : mergeModelSelectionOptionsById({ + current: current.textGenerationModelSelection.options, + patch: selectionPatch.options, + }); return { ...next, - textGenerationModelSelection: - provider === "codex" - ? { - provider, - model, - ...withModelSelectionOptions(selectionPatch.options as CodexModelOptions | undefined), - } - : provider === "claudeAgent" - ? { - provider, - model, - ...withModelSelectionOptions( - selectionPatch.options as ClaudeModelOptions | undefined, - ), - } - : provider === "cursor" - ? { - provider, - model, - ...withModelSelectionOptions( - selectionPatch.options as CursorModelOptions | undefined, - ), - } - : { - provider, - model, - ...withModelSelectionOptions( - selectionPatch.options as OpenCodeModelOptions | undefined, - ), - }, + textGenerationModelSelection: createModelSelection(provider, model, options), }; } From d5b7690f95504a04c12d10acfefd59f32515dbf2 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Apr 2026 11:24:06 -0400 Subject: [PATCH 12/20] Exclude subscribe RPCs from latency tracking (#2313) --- apps/web/src/rpc/requestLatencyState.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/rpc/requestLatencyState.ts b/apps/web/src/rpc/requestLatencyState.ts index d21e37b5298..ecc3b88275c 100644 --- a/apps/web/src/rpc/requestLatencyState.ts +++ b/apps/web/src/rpc/requestLatencyState.ts @@ -36,7 +36,7 @@ function getSlowRpcAckRequestsValue(): ReadonlyArray { } function shouldTrackRpcAck(tag: string): boolean { - return !tag.startsWith("subscribe"); + return !tag.includes("subscribe"); } export function getSlowRpcAckRequests(): ReadonlyArray { From 0ee302e24de2ff0a0f88b3478a3274560e28f3c9 Mon Sep 17 00:00:00 2001 From: Thimo Sietsma Date: Thu, 23 Apr 2026 17:42:35 +0200 Subject: [PATCH 13/20] fix(request-permission): add `dynamic_tool_call` to command request (#2311) --- apps/web/src/session-logic.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/session-logic.ts b/apps/web/src/session-logic.ts index 44e0498d7b9..fc009f9393f 100644 --- a/apps/web/src/session-logic.ts +++ b/apps/web/src/session-logic.ts @@ -169,6 +169,7 @@ function requestKindFromRequestType(requestType: unknown): PendingApproval["requ switch (requestType) { case "command_execution_approval": case "exec_command_approval": + case "dynamic_tool_call": return "command"; case "file_read_approval": return "file-read"; From 0d55a42840231122f8d7414b35f155343031f82d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 23 Apr 2026 16:52:02 +0100 Subject: [PATCH 14/20] fix(web): ignore stale runtime projection snapshots (#2301) --- .../src/environments/runtime/service.test.ts | 109 +++++++++++++++- apps/web/src/environments/runtime/service.ts | 121 ++++++++++++++++++ 2 files changed, 229 insertions(+), 1 deletion(-) diff --git a/apps/web/src/environments/runtime/service.test.ts b/apps/web/src/environments/runtime/service.test.ts index 7a4af404980..40ec455adc1 100644 --- a/apps/web/src/environments/runtime/service.test.ts +++ b/apps/web/src/environments/runtime/service.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vitest"; -import { shouldApplyTerminalEvent } from "./service"; +import { + shouldApplyProjectionEvent, + shouldApplyProjectionSnapshot, + shouldApplyTerminalEvent, +} from "./service"; describe("shouldApplyTerminalEvent", () => { it("applies terminal events for draft-only threads", () => { @@ -39,3 +43,106 @@ describe("shouldApplyTerminalEvent", () => { ).toBe(true); }); }); + +describe("shouldApplyProjectionSnapshot", () => { + it("accepts the first snapshot for an environment", () => { + expect( + shouldApplyProjectionSnapshot({ + current: null, + next: { + snapshotSequence: 1, + updatedAt: "2026-04-22T10:00:00.000Z", + }, + }), + ).toBe(true); + }); + + it("drops snapshots with an older sequence", () => { + expect( + shouldApplyProjectionSnapshot({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + next: { + snapshotSequence: 4, + updatedAt: "2026-04-22T10:06:00.000Z", + }, + }), + ).toBe(false); + }); + + it("drops snapshots with the same sequence and older timestamp", () => { + expect( + shouldApplyProjectionSnapshot({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + next: { + snapshotSequence: 5, + updatedAt: "2026-04-22T10:04:59.000Z", + }, + }), + ).toBe(false); + }); + + it("accepts snapshots with the same sequence and a newer timestamp", () => { + expect( + shouldApplyProjectionSnapshot({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + next: { + snapshotSequence: 5, + updatedAt: "2026-04-22T10:05:01.000Z", + }, + }), + ).toBe(true); + }); +}); + +describe("shouldApplyProjectionEvent", () => { + it("accepts the first event for an environment", () => { + expect( + shouldApplyProjectionEvent({ + current: null, + sequence: 1, + }), + ).toBe(true); + }); + + it("drops stale or duplicate events", () => { + expect( + shouldApplyProjectionEvent({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + sequence: 5, + }), + ).toBe(false); + expect( + shouldApplyProjectionEvent({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + sequence: 4, + }), + ).toBe(false); + }); + + it("accepts newer events", () => { + expect( + shouldApplyProjectionEvent({ + current: { + sequence: 5, + updatedAt: "2026-04-22T10:05:00.000Z", + }, + sequence: 6, + }), + ).toBe(true); + }); +}); diff --git a/apps/web/src/environments/runtime/service.ts b/apps/web/src/environments/runtime/service.ts index 775df0184eb..d724eeff333 100644 --- a/apps/web/src/environments/runtime/service.ts +++ b/apps/web/src/environments/runtime/service.ts @@ -87,6 +87,13 @@ type ThreadDetailSubscriptionEntry = { const environmentConnections = new Map(); const environmentConnectionListeners = new Set<() => void>(); const threadDetailSubscriptions = new Map(); +const lastAppliedProjectionVersionByEnvironment = new Map< + EnvironmentId, + { + readonly sequence: number; + readonly updatedAt: string | null; + } +>(); let activeService: EnvironmentServiceState | null = null; let needsProviderInvalidation = false; @@ -102,6 +109,98 @@ const THREAD_DETAIL_SUBSCRIPTION_IDLE_EVICTION_MS = 15 * 60 * 1000; const MAX_CACHED_THREAD_DETAIL_SUBSCRIPTIONS = 32; const NOOP = () => undefined; +function compareAppliedProjectionVersion( + left: { readonly sequence: number; readonly updatedAt: string | null }, + right: { readonly sequence: number; readonly updatedAt: string | null }, +): number { + if (left.sequence !== right.sequence) { + return left.sequence - right.sequence; + } + + const leftUpdatedAt = left.updatedAt ?? ""; + const rightUpdatedAt = right.updatedAt ?? ""; + if (leftUpdatedAt === rightUpdatedAt) { + return 0; + } + + return leftUpdatedAt < rightUpdatedAt ? -1 : 1; +} + +function toAppliedProjectionVersion( + snapshot: Pick, +): { + readonly sequence: number; + readonly updatedAt: string; +} { + return { + sequence: snapshot.snapshotSequence, + updatedAt: snapshot.updatedAt, + }; +} + +export function shouldApplyProjectionSnapshot(input: { + readonly current: { + readonly sequence: number; + readonly updatedAt: string | null; + } | null; + readonly next: Pick; +}): boolean { + if (input.current === null) { + return true; + } + + return compareAppliedProjectionVersion(input.current, toAppliedProjectionVersion(input.next)) < 0; +} + +export function shouldApplyProjectionEvent(input: { + readonly current: { + readonly sequence: number; + readonly updatedAt: string | null; + } | null; + readonly sequence: number; +}): boolean { + if (input.current === null) { + return true; + } + + return input.sequence > input.current.sequence; +} + +function readLastAppliedProjectionVersion(environmentId: EnvironmentId): { + readonly sequence: number; + readonly updatedAt: string | null; +} | null { + return lastAppliedProjectionVersionByEnvironment.get(environmentId) ?? null; +} + +function markAppliedProjectionSnapshot( + environmentId: EnvironmentId, + snapshot: Pick, +): void { + const nextVersion = toAppliedProjectionVersion(snapshot); + const currentVersion = readLastAppliedProjectionVersion(environmentId); + if ( + currentVersion !== null && + compareAppliedProjectionVersion(currentVersion, nextVersion) >= 0 + ) { + return; + } + + lastAppliedProjectionVersionByEnvironment.set(environmentId, nextVersion); +} + +function markAppliedProjectionEvent(environmentId: EnvironmentId, sequence: number): void { + const currentVersion = readLastAppliedProjectionVersion(environmentId); + if (currentVersion !== null && sequence <= currentVersion.sequence) { + return; + } + + lastAppliedProjectionVersionByEnvironment.set(environmentId, { + sequence, + updatedAt: currentVersion?.updatedAt ?? null, + }); +} + function getThreadDetailSubscriptionKey(environmentId: EnvironmentId, threadId: ThreadId): string { return scopedThreadKey(scopeThreadRef(environmentId, threadId)); } @@ -600,6 +699,15 @@ export function applyEnvironmentThreadDetailEvent( } function applyShellEvent(event: OrchestrationShellStreamEvent, environmentId: EnvironmentId) { + if ( + !shouldApplyProjectionEvent({ + current: readLastAppliedProjectionVersion(environmentId), + sequence: event.sequence, + }) + ) { + return; + } + const threadId = event.kind === "thread-upserted" ? event.thread.id @@ -610,6 +718,7 @@ function applyShellEvent(event: OrchestrationShellStreamEvent, environmentId: En const previousThread = threadRef ? selectThreadByRef(useStore.getState(), threadRef) : undefined; useStore.getState().applyShellEvent(event, environmentId); + markAppliedProjectionEvent(environmentId, event.sequence); switch (event.kind) { case "project-upserted": @@ -643,7 +752,17 @@ function createEnvironmentConnectionHandlers() { return { applyShellEvent, syncShellSnapshot: (snapshot: OrchestrationShellSnapshot, environmentId: EnvironmentId) => { + if ( + !shouldApplyProjectionSnapshot({ + current: readLastAppliedProjectionVersion(environmentId), + next: snapshot, + }) + ) { + return; + } + useStore.getState().syncServerShellSnapshot(snapshot, environmentId); + markAppliedProjectionSnapshot(environmentId, snapshot); reconcileThreadDetailSubscriptionsForEnvironment( environmentId, snapshot.threads.map((thread) => thread.id), @@ -758,6 +877,7 @@ async function removeConnection(environmentId: EnvironmentId): Promise } disposeThreadDetailSubscriptionsForEnvironment(environmentId); + lastAppliedProjectionVersionByEnvironment.delete(environmentId); environmentConnections.delete(environmentId); emitEnvironmentConnectionRegistryChange(); await connection.dispose(); @@ -1086,6 +1206,7 @@ export function startEnvironmentConnectionService(queryClient: QueryClient): () export async function resetEnvironmentServiceForTests(): Promise { stopActiveService(); + lastAppliedProjectionVersionByEnvironment.clear(); for (const key of Array.from(threadDetailSubscriptions.keys())) { disposeThreadDetailSubscriptionByKey(key); } From 188df6da074bf60af765881bdfd6e886ef83e6ee Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 23 Apr 2026 13:29:54 -0400 Subject: [PATCH 15/20] Fix Claude session cwd resume drift (#2292) Co-authored-by: Cursor Agent Co-authored-by: Julius Marminge Co-authored-by: codex --- .../Layers/ProviderCommandReactor.test.ts | 76 +++++++++++++++ .../Layers/ProviderCommandReactor.ts | 6 ++ .../src/provider/Layers/ClaudeAdapter.test.ts | 90 ++++++++++++++++++ .../src/provider/Layers/ClaudeAdapter.ts | 40 ++++++++ .../provider/Layers/ProviderService.test.ts | 95 ++++++++++++++++++- .../src/provider/Layers/ProviderService.ts | 24 ++++- 6 files changed, 327 insertions(+), 4 deletions(-) diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index a52ed5856e8..80f571285dd 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -138,6 +138,12 @@ describe("ProviderCommandReactor", () => { (input.runtimeMode === "approval-required" || input.runtimeMode === "full-access") ? input.runtimeMode : "full-access", + ...(typeof input === "object" && + input !== null && + "cwd" in input && + typeof input.cwd === "string" + ? { cwd: input.cwd } + : {}), ...(modelSelection.model !== undefined ? { model: modelSelection.model } : {}), threadId, resumeCursor: resumeCursor ?? { opaque: `resume-${sessionIndex}` }, @@ -864,6 +870,76 @@ describe("ProviderCommandReactor", () => { expect(harness.stopSession.mock.calls.length).toBe(0); }); + it("restarts the provider session when the thread workspace changes", async () => { + const harness = await createHarness({ + threadModelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, + }); + const now = new Date().toISOString(); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.start", + commandId: CommandId.make("cmd-turn-start-workspace-1"), + threadId: ThreadId.make("thread-1"), + message: { + messageId: asMessageId("user-message-workspace-1"), + role: "user", + text: "first in project root", + attachments: [], + }, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + createdAt: now, + }), + ); + + await waitFor(() => harness.startSession.mock.calls.length === 1); + await waitFor(() => harness.sendTurn.mock.calls.length === 1); + expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ + cwd: "/tmp/provider-project", + }); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.meta.update", + commandId: CommandId.make("cmd-thread-worktree-change"), + threadId: ThreadId.make("thread-1"), + worktreePath: "/tmp/provider-project-worktree", + }), + ); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.start", + commandId: CommandId.make("cmd-turn-start-workspace-2"), + threadId: ThreadId.make("thread-1"), + message: { + messageId: asMessageId("user-message-workspace-2"), + role: "user", + text: "second in worktree", + attachments: [], + }, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + createdAt: now, + }), + ); + + await waitFor(() => harness.startSession.mock.calls.length === 2); + await waitFor(() => harness.sendTurn.mock.calls.length === 2); + expect(harness.stopSession.mock.calls.length).toBe(0); + expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({ + threadId: ThreadId.make("thread-1"), + cwd: "/tmp/provider-project-worktree", + resumeCursor: { opaque: "resume-1" }, + modelSelection: { + provider: "claudeAgent", + model: "claude-sonnet-4-6", + }, + runtimeMode: "approval-required", + }); + }); + it("restarts claude sessions when claude effort changes", async () => { const harness = await createHarness({ threadModelSelection: { provider: "claudeAgent", model: "claude-sonnet-4-6" }, diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index 8b3321ec3ea..f7ae38b2d2a 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -334,6 +334,7 @@ const make = Effect.gen(function* () { thread.session && thread.session.status !== "stopped" && activeSession ? thread.id : null; if (existingSessionThreadId) { const runtimeModeChanged = thread.runtimeMode !== thread.session?.runtimeMode; + const cwdChanged = effectiveCwd !== activeSession?.cwd; const sessionModelSwitch = currentProvider === undefined ? "in-session" @@ -350,6 +351,7 @@ const make = Effect.gen(function* () { if ( !runtimeModeChanged && + !cwdChanged && !shouldRestartForModelChange && !shouldRestartForModelSelectionChange ) { @@ -367,6 +369,9 @@ const make = Effect.gen(function* () { currentRuntimeMode: thread.session?.runtimeMode, desiredRuntimeMode: thread.runtimeMode, runtimeModeChanged, + previousCwd: activeSession?.cwd, + desiredCwd: effectiveCwd, + cwdChanged, modelChanged, shouldRestartForModelChange, shouldRestartForModelSelectionChange, @@ -381,6 +386,7 @@ const make = Effect.gen(function* () { restartedSessionThreadId: restartedSession.threadId, provider: restartedSession.provider, runtimeMode: restartedSession.runtimeMode, + cwd: restartedSession.cwd, }); yield* bindSessionToThread(restartedSession); return restartedSession.threadId; diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 07ea11f0d63..9c83bce8584 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -2561,6 +2561,96 @@ describe("ClaudeAdapterLive", () => { ); }); + it.effect("preserves durable resume ids across Claude resume hooks", () => { + const harness = makeHarness(); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + const durableSessionId = "550e8400-e29b-41d4-a716-446655440000"; + const transientHookSessionId = "7368d0c7-40a3-4d8a-bcc1-ac80c49f2719"; + + const runtimeEventsFiber = yield* Stream.take(adapter.streamEvents, 7).pipe( + Stream.runCollect, + Effect.forkChild, + ); + + yield* adapter.startSession({ + threadId: RESUME_THREAD_ID, + provider: "claudeAgent", + resumeCursor: { + threadId: RESUME_THREAD_ID, + resume: durableSessionId, + resumeSessionAt: "assistant-99", + turnCount: 3, + }, + runtimeMode: "full-access", + }); + + harness.query.emit({ + type: "system", + subtype: "hook_started", + hook_id: "resume-hook-1", + hook_name: "SessionStart:resume", + hook_event: "SessionStart", + session_id: transientHookSessionId, + uuid: "resume-hook-started", + } as unknown as SDKMessage); + + harness.query.emit({ + type: "system", + subtype: "hook_response", + hook_id: "resume-hook-1", + hook_name: "SessionStart:resume", + hook_event: "SessionStart", + output: "", + stdout: "", + stderr: "", + outcome: "success", + session_id: transientHookSessionId, + uuid: "resume-hook-response", + } as unknown as SDKMessage); + + harness.query.emit({ + type: "system", + subtype: "init", + apiKeySource: "none", + claude_code_version: "test", + cwd: "/tmp/claude-adapter-test", + tools: [], + mcp_servers: [], + model: "claude-sonnet-4-5", + permissionMode: "bypassPermissions", + slash_commands: [], + output_style: "default", + skills: [], + plugins: [], + session_id: durableSessionId, + uuid: "resume-init", + } as unknown as SDKMessage); + + const runtimeEvents = Array.from(yield* Fiber.join(runtimeEventsFiber)); + const threadStartedEvents = runtimeEvents.filter((event) => event.type === "thread.started"); + assert.equal(threadStartedEvents.length, 1); + const threadStarted = threadStartedEvents[0]; + assert.equal(threadStarted?.type, "thread.started"); + if (threadStarted?.type === "thread.started") { + assert.deepEqual(threadStarted.payload, { + providerThreadId: durableSessionId, + }); + } + + const activeSessions = yield* adapter.listSessions(); + const resumeCursor = activeSessions[0]?.resumeCursor as + | { + readonly resume?: string; + } + | undefined; + assert.equal(resumeCursor?.resume, durableSessionId); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + it.effect("uses an app-generated Claude session id for fresh sessions", () => { const harness = makeHarness(); return Effect.gen(function* () { diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 03dfffa0426..35f326af07b 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -198,6 +198,18 @@ function isSyntheticClaudeThreadId(value: string): boolean { return value.startsWith("claude-thread-"); } +function hasDurableClaudeSessionId(message: SDKMessage): boolean { + if (message.type !== "system") { + return true; + } + + return ( + message.subtype !== "hook_started" && + message.subtype !== "hook_progress" && + message.subtype !== "hook_response" + ); +} + function toMessage(cause: unknown, fallback: string): string { if (cause instanceof Error && cause.message.length > 0) { return cause.message; @@ -1249,6 +1261,9 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( if (typeof message.session_id !== "string" || message.session_id.length === 0) { return; } + if (!hasDurableClaudeSessionId(message)) { + return; + } const nextThreadId = message.session_id; context.resumeSessionId = message.session_id; yield* updateResumeCursor(context); @@ -2875,6 +2890,31 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( ...(Object.keys(extraArgs).length > 0 ? { extraArgs } : {}), }; + yield* Effect.annotateCurrentSpan({ + "provider.kind": PROVIDER, + "provider.thread_id": threadId, + "provider.runtime_mode": input.runtimeMode, + "claude.resume.source": + existingResumeSessionId !== undefined ? "resume-session" : "generated-session", + "claude.resume.thread_id": resumeState?.threadId ?? "", + "claude.resume.session_id": existingResumeSessionId ?? "", + "claude.resume.session_at": resumeState?.resumeSessionAt ?? "", + "claude.resume.turn_count": resumeState?.turnCount ?? -1, + "claude.query.cwd": input.cwd ?? "", + "claude.query.model": apiModelId ?? "", + "claude.query.effort": effectiveEffort ?? "", + "claude.query.permission_mode": permissionMode ?? "", + "claude.query.allow_dangerously_skip_permissions": permissionMode === "bypassPermissions", + "claude.query.resume": existingResumeSessionId ?? "", + "claude.query.session_id": newSessionId ?? "", + "claude.query.include_partial_messages": true, + "claude.query.additional_directories": input.cwd ? [input.cwd] : [], + "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], + "claude.query.settings_json": JSON.stringify(settings), + "claude.query.extra_args_json": JSON.stringify(extraArgs), + "claude.query.path_to_executable": claudeBinaryPath, + }); + const queryRuntime = yield* Effect.try({ try: () => createQuery({ diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index d7b8c1cb38a..09606ba35a7 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -929,9 +929,10 @@ routing.layer("ProviderServiceLive routing", (it) => { const provider = yield* ProviderService; const runtimeRepository = yield* ProviderSessionRuntimeRepository; - const session = yield* provider.startSession(asThreadId("thread-1"), { + const threadId = asThreadId("thread-runtime-status"); + const session = yield* provider.startSession(threadId, { provider: "codex", - threadId: asThreadId("thread-1"), + threadId, runtimeMode: "full-access", }); yield* provider.sendTurn({ @@ -957,7 +958,7 @@ routing.layer("ProviderServiceLive routing", (it) => { lastError: string | null; lastRuntimeEvent: string | null; }; - assert.equal(runtimePayload.cwd, process.cwd()); + assert.equal(runtimePayload.cwd, session.cwd); assert.equal(runtimePayload.model, null); assert.equal(runtimePayload.activeTurnId, `turn-${String(session.threadId)}`); assert.equal(runtimePayload.lastError, null); @@ -1058,6 +1059,94 @@ routing.layer("ProviderServiceLive routing", (it) => { fs.rmSync(tempDir, { recursive: true, force: true }); }).pipe(Effect.provide(NodeServices.layer)), ); + + it.effect( + "reuses persisted cwd when startSession resumes a claude session without cwd input", + () => + Effect.gen(function* () { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "t3-provider-service-cwd-")); + const dbPath = path.join(tempDir, "orchestration.sqlite"); + const persistenceLayer = makeSqlitePersistenceLive(dbPath); + const runtimeRepositoryLayer = ProviderSessionRuntimeRepositoryLive.pipe( + Layer.provide(persistenceLayer), + ); + + const firstClaude = makeFakeCodexAdapter("claudeAgent"); + const firstRegistry: typeof ProviderAdapterRegistry.Service = { + getByProvider: (provider) => + provider === "claudeAgent" + ? Effect.succeed(firstClaude.adapter) + : Effect.fail(new ProviderUnsupportedError({ provider })), + listProviders: () => Effect.succeed(["claudeAgent"]), + }; + const firstDirectoryLayer = ProviderSessionDirectoryLive.pipe( + Layer.provide(runtimeRepositoryLayer), + ); + const firstProviderLayer = makeProviderServiceLive().pipe( + Layer.provide(Layer.succeed(ProviderAdapterRegistry, firstRegistry)), + Layer.provide(firstDirectoryLayer), + Layer.provide(defaultServerSettingsLayer), + Layer.provide(AnalyticsService.layerTest), + ); + + const initial = yield* Effect.gen(function* () { + const provider = yield* ProviderService; + return yield* provider.startSession(asThreadId("thread-claude-cwd"), { + provider: "claudeAgent", + threadId: asThreadId("thread-claude-cwd"), + cwd: "/tmp/project-claude-cwd", + runtimeMode: "full-access", + }); + }).pipe(Effect.provide(firstProviderLayer)); + + const secondClaude = makeFakeCodexAdapter("claudeAgent"); + const secondRegistry: typeof ProviderAdapterRegistry.Service = { + getByProvider: (provider) => + provider === "claudeAgent" + ? Effect.succeed(secondClaude.adapter) + : Effect.fail(new ProviderUnsupportedError({ provider })), + listProviders: () => Effect.succeed(["claudeAgent"]), + }; + const secondDirectoryLayer = ProviderSessionDirectoryLive.pipe( + Layer.provide(runtimeRepositoryLayer), + ); + const secondProviderLayer = makeProviderServiceLive().pipe( + Layer.provide(Layer.succeed(ProviderAdapterRegistry, secondRegistry)), + Layer.provide(secondDirectoryLayer), + Layer.provide(defaultServerSettingsLayer), + Layer.provide(AnalyticsService.layerTest), + ); + + secondClaude.startSession.mockClear(); + + yield* Effect.gen(function* () { + const provider = yield* ProviderService; + yield* provider.startSession(initial.threadId, { + provider: "claudeAgent", + threadId: initial.threadId, + runtimeMode: "full-access", + }); + }).pipe(Effect.provide(secondProviderLayer)); + + assert.equal(secondClaude.startSession.mock.calls.length, 1); + const resumedStartInput = secondClaude.startSession.mock.calls[0]?.[0]; + assert.equal(typeof resumedStartInput === "object" && resumedStartInput !== null, true); + if (resumedStartInput && typeof resumedStartInput === "object") { + const startPayload = resumedStartInput as { + provider?: string; + cwd?: string; + resumeCursor?: unknown; + threadId?: string; + }; + assert.equal(startPayload.provider, "claudeAgent"); + assert.equal(startPayload.cwd, "/tmp/project-claude-cwd"); + assert.deepEqual(startPayload.resumeCursor, initial.resumeCursor); + assert.equal(startPayload.threadId, initial.threadId); + } + + fs.rmSync(tempDir, { recursive: true, force: true }); + }).pipe(Effect.provide(NodeServices.layer)), + ); }); const fanout = makeProviderServiceLayer(); diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index a38a24655fe..94630d3bca9 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -374,9 +374,31 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( (persistedBinding?.provider === input.provider ? persistedBinding.resumeCursor : undefined); + const effectiveCwd = + input.cwd ?? + (persistedBinding?.provider === input.provider + ? readPersistedCwd(persistedBinding.runtimePayload) + : undefined); + yield* Effect.annotateCurrentSpan({ + "provider.resume_cursor.source": + input.resumeCursor !== undefined + ? "request" + : effectiveResumeCursor !== undefined && persistedBinding?.provider === input.provider + ? "persisted" + : "none", + "provider.resume_cursor.present": effectiveResumeCursor !== undefined, + "provider.cwd.source": + input.cwd !== undefined + ? "request" + : effectiveCwd !== undefined && persistedBinding?.provider === input.provider + ? "persisted" + : "none", + "provider.cwd.effective": effectiveCwd ?? "", + }); const adapter = yield* registry.getByProvider(input.provider); const session = yield* adapter.startSession({ ...input, + ...(effectiveCwd !== undefined ? { cwd: effectiveCwd } : {}), ...(effectiveResumeCursor !== undefined ? { resumeCursor: effectiveResumeCursor } : {}), }); @@ -398,7 +420,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( provider: session.provider, runtimeMode: input.runtimeMode, hasResumeCursor: session.resumeCursor !== undefined, - hasCwd: typeof input.cwd === "string" && input.cwd.trim().length > 0, + hasCwd: typeof effectiveCwd === "string" && effectiveCwd.trim().length > 0, hasModel: typeof input.modelSelection?.model === "string" && input.modelSelection.model.trim().length > 0, From 00b5c3e1b3b06888282e5d5e0678a2e3a88eb607 Mon Sep 17 00:00:00 2001 From: legs <145564979+justsomelegs@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:59:08 +0100 Subject: [PATCH 16/20] Add task sidebar auto-open setting (#2314) --- apps/desktop/src/clientPersistence.test.ts | 1 + apps/web/src/components/ChatView.tsx | 19 +++++++++--- .../components/settings/SettingsPanels.tsx | 30 +++++++++++++++++++ apps/web/src/localApi.test.ts | 2 ++ packages/contracts/src/settings.ts | 2 ++ 5 files changed, 50 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/clientPersistence.test.ts b/apps/desktop/src/clientPersistence.test.ts index 19f9b09f9fd..192d7ac1064 100644 --- a/apps/desktop/src/clientPersistence.test.ts +++ b/apps/desktop/src/clientPersistence.test.ts @@ -49,6 +49,7 @@ function makeSecretStorage(available: boolean): DesktopSecretStorage { } const clientSettings: ClientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 3a71922706b..c27afda975b 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -616,6 +616,7 @@ export default function ChatView(props: ChatViewProps) { (store) => store.setStickyModelSelection, ); const timestampFormat = settings.timestampFormat; + const autoOpenPlanSidebar = settings.autoOpenPlanSidebar; const navigate = useNavigate(); const rawSearch = useSearch({ strict: false, @@ -2010,6 +2011,7 @@ export default function ChatView(props: ChatViewProps) { planSidebarOpenOnNextThreadRef.current = false; setPlanSidebarOpen(true); } else { + planSidebarOpenOnNextThreadRef.current = false; setPlanSidebarOpen(false); } planSidebarDismissedForTurnRef.current = null; @@ -2018,6 +2020,7 @@ export default function ChatView(props: ChatViewProps) { // Auto-open the plan sidebar when plan/todo steps arrive for the current turn. // Don't auto-open for plans carried over from a previous turn (the user can open manually). useEffect(() => { + if (!autoOpenPlanSidebar) return; if (!activePlan) return; if (planSidebarOpen) return; const latestTurnId = activeLatestTurn?.turnId ?? null; @@ -2025,7 +2028,13 @@ export default function ChatView(props: ChatViewProps) { const turnKey = activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__"; if (planSidebarDismissedForTurnRef.current === turnKey) return; setPlanSidebarOpen(true); - }, [activePlan, activeLatestTurn?.turnId, planSidebarOpen, sidebarProposedPlan?.turnId]); + }, [ + activePlan, + activeLatestTurn?.turnId, + autoOpenPlanSidebar, + planSidebarOpen, + sidebarProposedPlan?.turnId, + ]); useEffect(() => { setIsRevertingCheckpoint(false); @@ -2950,7 +2959,7 @@ export default function ChatView(props: ChatViewProps) { // Optimistically open the plan sidebar when implementing (not refining). // "default" mode here means the agent is executing the plan, which produces // step-tracking activities that the sidebar will display. - if (nextInteractionMode === "default") { + if (nextInteractionMode === "default" && autoOpenPlanSidebar) { planSidebarDismissedForTurnRef.current = null; setPlanSidebarOpen(true); } @@ -2979,6 +2988,7 @@ export default function ChatView(props: ChatViewProps) { runtimeMode, setComposerDraftInteractionMode, setThreadError, + autoOpenPlanSidebar, environmentId, ], ); @@ -3071,8 +3081,8 @@ export default function ChatView(props: ChatViewProps) { return waitForStartedServerThread(scopeThreadRef(activeThread.environmentId, nextThreadId)); }) .then(() => { - // Signal that the plan sidebar should open on the new thread. - planSidebarOpenOnNextThreadRef.current = true; + // Signal that the plan sidebar should open on the new thread when enabled. + planSidebarOpenOnNextThreadRef.current = autoOpenPlanSidebar; return navigate({ to: "/$environmentId/$threadId", params: { @@ -3113,6 +3123,7 @@ export default function ChatView(props: ChatViewProps) { navigate, resetLocalDispatch, runtimeMode, + autoOpenPlanSidebar, environmentId, ]); diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 1e8cffd0c4a..29d09a5cdce 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -472,6 +472,9 @@ export function useSettingsRestore(onRestored?: () => void) { ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap ? ["Diff line wrapping"] : []), + ...(settings.autoOpenPlanSidebar !== DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar + ? ["Task sidebar"] + : []), ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? ["Assistant output"] : []), @@ -493,6 +496,7 @@ export function useSettingsRestore(onRestored?: () => void) { [ areProviderSettingsDirty, isGitWritingModelDirty, + settings.autoOpenPlanSidebar, settings.confirmThreadArchive, settings.confirmThreadDelete, settings.addProjectBaseDirectory, @@ -945,6 +949,32 @@ export function GeneralSettingsPanel() { } /> + + updateSettings({ + autoOpenPlanSidebar: DEFAULT_UNIFIED_SETTINGS.autoOpenPlanSidebar, + }) + } + /> + ) : null + } + control={ + + updateSettings({ autoOpenPlanSidebar: Boolean(checked) }) + } + aria-label="Open the task sidebar automatically" + /> + } + /> + { it("reads and writes persistence through the desktop bridge when available", async () => { const clientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, @@ -587,6 +588,7 @@ describe("wsApi", () => { const { createLocalApi } = await import("./localApi"); const api = createLocalApi(rpcClientMock as never); const clientSettings = { + autoOpenPlanSidebar: false, confirmThreadArchive: true, confirmThreadDelete: false, diffWordWrap: true, diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 6301364f337..2b50957a79b 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -31,6 +31,7 @@ export type SidebarProjectGroupingMode = typeof SidebarProjectGroupingMode.Type; export const DEFAULT_SIDEBAR_PROJECT_GROUPING_MODE: SidebarProjectGroupingMode = "repository"; export const ClientSettingsSchema = Schema.Struct({ + autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), diffWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), @@ -243,6 +244,7 @@ export const ServerSettingsPatch = Schema.Struct({ export type ServerSettingsPatch = typeof ServerSettingsPatch.Type; export const ClientSettingsPatch = Schema.Struct({ + autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean), confirmThreadArchive: Schema.optionalKey(Schema.Boolean), confirmThreadDelete: Schema.optionalKey(Schema.Boolean), diffWordWrap: Schema.optionalKey(Schema.Boolean), From ada410bccff144ce4cfed0e2c6e18974b045f968 Mon Sep 17 00:00:00 2001 From: "t3-code[bot]" <269035359+t3-code[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:05:15 +0000 Subject: [PATCH 17/20] chore(release): prepare v0.0.21 --- apps/desktop/package.json | 2 +- apps/server/package.json | 2 +- apps/web/package.json | 2 +- bun.lock | 8 ++++---- packages/contracts/package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2a4ced70e77..165395507ef 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@t3tools/desktop", - "version": "0.0.20", + "version": "0.0.21", "private": true, "type": "module", "main": "dist-electron/main.cjs", diff --git a/apps/server/package.json b/apps/server/package.json index 14dbe35bcba..13a8124cb26 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "t3", - "version": "0.0.20", + "version": "0.0.21", "license": "MIT", "repository": { "type": "git", diff --git a/apps/web/package.json b/apps/web/package.json index b18defebbe3..11e69d1248d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@t3tools/web", - "version": "0.0.20", + "version": "0.0.21", "private": true, "type": "module", "scripts": { diff --git a/bun.lock b/bun.lock index e9b7511e340..a8dc482f464 100644 --- a/bun.lock +++ b/bun.lock @@ -14,7 +14,7 @@ }, "apps/desktop": { "name": "@t3tools/desktop", - "version": "0.0.20", + "version": "0.0.21", "dependencies": { "effect": "catalog:", "electron": "40.6.0", @@ -43,7 +43,7 @@ }, "apps/server": { "name": "t3", - "version": "0.0.20", + "version": "0.0.21", "bin": { "t3": "./dist/bin.mjs", }, @@ -75,7 +75,7 @@ }, "apps/web": { "name": "@t3tools/web", - "version": "0.0.20", + "version": "0.0.21", "dependencies": { "@base-ui/react": "^1.2.0", "@dnd-kit/core": "^6.3.1", @@ -140,7 +140,7 @@ }, "packages/contracts": { "name": "@t3tools/contracts", - "version": "0.0.20", + "version": "0.0.21", "dependencies": { "effect": "catalog:", }, diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 8b499267f66..0e436d64d5e 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@t3tools/contracts", - "version": "0.0.20", + "version": "0.0.21", "private": true, "files": [ "dist" From aecf1b288144395ea46f7a6e992dce97f587c265 Mon Sep 17 00:00:00 2001 From: sherlock Date: Fri, 24 Apr 2026 21:42:54 +0530 Subject: [PATCH 18/20] ci: bump quality job timeout to 30min MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 10min timeout was too tight — install + lint + typecheck + test (13min locally) + browser test + build doesn't fit. Hit the wall on first uncached sync/upstream branch install. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2683172e646..5289c4ee17d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: quality: name: Format, Lint, Typecheck, Test, Browser Test, Build runs-on: ubuntu-24.04 - timeout-minutes: 10 + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@v6 From 5acd9ed97556500380f9b59df58aaad72f01cc49 Mon Sep 17 00:00:00 2001 From: sherlock Date: Sat, 25 Apr 2026 14:13:40 +0530 Subject: [PATCH 19/20] review: address CodeRabbit feedback on sync PR - ClaudeAdapter.ts: redact extraArgs / settings JSON from span attributes (avoid telemetry leaking secrets); record key list + arg count instead. - ProviderService.test.ts: pass cwd explicitly to startSession so the runtimePayload.cwd assertion is self-contained. - toast.logic.ts: fall back to first visible toast when all are 'ending' so frontmostHeight doesn't collapse to 0 mid-exit animation. - toast.tsx: hide corner dismiss orb on collapsed (peek) toasts so it matches the body opacity behavior. - requestLatencyState.ts: match 'subscribe' as a leading segment so RPCs like thread/unsubscribe still get tracked. --- apps/server/src/provider/Layers/ClaudeAdapter.ts | 4 ++-- apps/server/src/provider/Layers/ProviderService.test.ts | 1 + apps/web/src/components/ui/toast.logic.ts | 9 ++++++--- apps/web/src/components/ui/toast.tsx | 8 +++++++- apps/web/src/rpc/requestLatencyState.ts | 4 +++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index a04e855ba3d..3b32646ec40 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -3027,8 +3027,8 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( "claude.query.include_partial_messages": true, "claude.query.additional_directories": input.cwd ? [input.cwd] : [], "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES], - "claude.query.settings_json": JSON.stringify(settings), - "claude.query.extra_args_json": JSON.stringify(extraArgs), + "claude.query.settings_keys": Object.keys(settings).sort(), + "claude.query.extra_args_count": Object.keys(extraArgs).length, "claude.query.path_to_executable": claudeBinaryPath, }); diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index b26e668a146..9bb40596a06 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -937,6 +937,7 @@ routing.layer("ProviderServiceLive routing", (it) => { const session = yield* provider.startSession(asThreadId("thread-1"), { provider: "codex", threadId: asThreadId("thread-1"), + cwd: "/tmp/project-send-turn", runtimeMode: "full-access", }); yield* provider.sendTurn({ diff --git a/apps/web/src/components/ui/toast.logic.ts b/apps/web/src/components/ui/toast.logic.ts index 80f23970cee..c7c196efd8c 100644 --- a/apps/web/src/components/ui/toast.logic.ts +++ b/apps/web/src/components/ui/toast.logic.ts @@ -76,11 +76,14 @@ export function buildVisibleToastLayout( ); // Frontmost height should reflect the first non-ending (live) toast so the - // stack sizes to what's actually staying on screen. - const frontmostLiveToast = visibleToasts.find((toast) => toast.transitionStatus !== "ending"); + // stack sizes to what's actually staying on screen. While the last visible + // toast is exiting, fall back to it so the stack doesn't collapse to 0 + // before the exit animation finishes. + const frontmostToast = + visibleToasts.find((toast) => toast.transitionStatus !== "ending") ?? visibleToasts[0]; return { - frontmostHeight: normalizeToastHeight(frontmostLiveToast?.height), + frontmostHeight: normalizeToastHeight(frontmostToast?.height), items, }; } diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index c20366833f0..81c891cd37d 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -575,7 +575,13 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { dismissAfterVisibleMs={toast.data?.dismissAfterVisibleMs} toastId={toast.id} /> -
+